Add JdbcOperationsSessionRepository
This commit provides implementation of SessionRepository based on Spring's JdbcOperations interface. @EnableJdbcHttpSession annotation is provided to ease the configuration, together with spring-session-jdbc BOM and schema creation scripts for all major databases. Fixes gh-364
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.PreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.AdditionalMatchers.not;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.contains;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Matchers.startsWith;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link JdbcOperationsSessionRepository}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private DataSource dataSource;
|
||||
|
||||
@Mock
|
||||
private JdbcOperations jdbcOperations;
|
||||
|
||||
private JdbcOperationsSessionRepository repository;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorDataSource() {
|
||||
JdbcOperationsSessionRepository repository =
|
||||
new JdbcOperationsSessionRepository(this.dataSource);
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(repository, "jdbcOperations")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullDataSource() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Property 'dataSource' is required");
|
||||
|
||||
new JdbcOperationsSessionRepository((DataSource) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorNullJdbcOperations() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("JdbcOperations must not be null");
|
||||
|
||||
new JdbcOperationsSessionRepository((JdbcOperations) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTableNameNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Table name must not be empty");
|
||||
|
||||
this.repository.setTableName(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTableNameEmpty() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Table name must not be empty");
|
||||
|
||||
this.repository.setTableName(" ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setLobHandlerNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("LobHandler must not be null");
|
||||
|
||||
this.repository.setLobHandler(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSerializingConverterNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("SerializingConverter must not be null");
|
||||
|
||||
this.repository.setSerializingConverter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeserializingConverterNull() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("DeserializingConverter must not be null");
|
||||
|
||||
this.repository.setDeserializingConverter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionDefaultMaxInactiveInterval() throws Exception {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.isNew()).isTrue();
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(
|
||||
new MapSession().getMaxInactiveIntervalInSeconds());
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionCustomMaxInactiveInterval() throws Exception {
|
||||
int interval = 1;
|
||||
this.repository.setDefaultMaxInactiveInterval(interval);
|
||||
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
|
||||
assertThat(session.isNew()).isTrue();
|
||||
assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(interval);
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNew() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.isNew()).isFalse();
|
||||
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"), isA(PreparedStatementSetter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUpdatedAttributes() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession());
|
||||
session.setAttribute("testName", "testValue");
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.isNew()).isFalse();
|
||||
verify(this.jdbcOperations, times(1))
|
||||
.update(and(startsWith("UPDATE"), contains("SESSION_BYTES")), isA(PreparedStatementSetter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUpdatedLastAccessedTime() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession());
|
||||
session.setLastAccessedTime(System.currentTimeMillis());
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.isNew()).isFalse();
|
||||
verify(this.jdbcOperations, times(1))
|
||||
.update(and(startsWith("UPDATE"), not(contains("SESSION_BYTES"))), isA(PreparedStatementSetter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveUnchanged() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession());
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
assertThat(session.isNew()).isFalse();
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionNotFound() {
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(sessionId);
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.jdbcOperations, times(1)).queryForObject(
|
||||
startsWith("SELECT"), eq(new Object[] { sessionId }), isA(RowMapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionExpired() {
|
||||
MapSession expired = new MapSession();
|
||||
expired.setMaxInactiveIntervalInSeconds(0);
|
||||
when(this.jdbcOperations.queryForObject(startsWith("SELECT"), eq(new Object[] { expired.getId() }), isA(RowMapper.class)))
|
||||
.thenReturn(expired);
|
||||
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(expired.getId());
|
||||
|
||||
assertThat(session).isNull();
|
||||
verify(this.jdbcOperations, times(1)).queryForObject(
|
||||
startsWith("SELECT"), eq(new Object[] { expired.getId() }), isA(RowMapper.class));
|
||||
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(expired.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionFound() {
|
||||
MapSession saved = new MapSession();
|
||||
saved.setAttribute("savedName", "savedValue");
|
||||
when(this.jdbcOperations.queryForObject(startsWith("SELECT"), eq(new Object[] { saved.getId() }), isA(RowMapper.class)))
|
||||
.thenReturn(saved);
|
||||
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.getSession(saved.getId());
|
||||
|
||||
assertThat(session.getId()).isEqualTo(saved.getId());
|
||||
assertThat(session.isNew()).isFalse();
|
||||
assertThat(session.getAttribute("savedName")).isEqualTo("savedValue");
|
||||
verify(this.jdbcOperations, times(1)).queryForObject(
|
||||
startsWith("SELECT"), eq(new Object[] { saved.getId() }), isA(RowMapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
String sessionId = "testSessionId";
|
||||
|
||||
this.repository.delete(sessionId);
|
||||
|
||||
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(sessionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValueUnknownIndexName() {
|
||||
String indexValue = "testIndexValue";
|
||||
|
||||
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions =
|
||||
this.repository.findByIndexNameAndIndexValue("testIndexName", indexValue);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
|
||||
String principal = "username";
|
||||
|
||||
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).isEmpty();
|
||||
verify(this.jdbcOperations, times(1)).query(
|
||||
startsWith("SELECT"), eq(new Object[] { principal }), isA(RowMapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
|
||||
String principal = "username";
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(
|
||||
principal, "notused", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
List<MapSession> saved = new ArrayList<MapSession>(2);
|
||||
MapSession saved1 = new MapSession();
|
||||
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved1);
|
||||
MapSession saved2 = new MapSession();
|
||||
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved2);
|
||||
when(this.jdbcOperations.query(startsWith("SELECT"), eq(new Object[] { principal }), isA(RowMapper.class)))
|
||||
.thenReturn(saved);
|
||||
|
||||
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
|
||||
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
|
||||
|
||||
assertThat(sessions).hasSize(2);
|
||||
verify(this.jdbcOperations, times(1)).query(
|
||||
startsWith("SELECT"), eq(new Object[] { principal }), isA(RowMapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cleanupExpiredSessions() {
|
||||
this.repository.cleanUpExpiredSessions();
|
||||
|
||||
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.session.jdbc.config.annotation.web.http;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link JdbcHttpSessionConfiguration}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class JdbcHttpSessionConfigurationTests {
|
||||
|
||||
private static final String TABLE_NAME = "TEST_SESSION";
|
||||
|
||||
private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600;
|
||||
|
||||
private static final String TABLE_NAME_SYSTEM_PROPERTY = "spring.session.jdbc.tableName";
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
|
||||
@After
|
||||
public void closeContext() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noDataSourceConfiguration() {
|
||||
this.thrown.expect(UnsatisfiedDependencyException.class);
|
||||
this.thrown.expectMessage("springSessionJdbcOperations");
|
||||
|
||||
registerAndRefresh(EmptyConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultConfiguration() {
|
||||
registerAndRefresh(DefaultConfiguration.class);
|
||||
|
||||
assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customTableName() {
|
||||
registerAndRefresh(CustomTableNameConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customTableNameSystemProperty() {
|
||||
System.setProperty(TABLE_NAME_SYSTEM_PROPERTY, TABLE_NAME);
|
||||
|
||||
try {
|
||||
registerAndRefresh(DefaultConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME);
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(TABLE_NAME_SYSTEM_PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMaxInactiveIntervalInSeconds() {
|
||||
registerAndRefresh(CustomMaxInactiveIntervalInSecondsConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
|
||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customLobHandlerConfiguration() {
|
||||
registerAndRefresh(CustomLobHandlerConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
LobHandler lobHandler = this.context.getBean(LobHandler.class);
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(lobHandler).isNotNull();
|
||||
assertThat(ReflectionTestUtils.getField(repository, "lobHandler")).isEqualTo(lobHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void customSerializingConverterConfiguration() {
|
||||
registerAndRefresh(CustomSerializingConverterConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
Converter<Object, byte[]> serializingConverter =
|
||||
(Converter<Object, byte[]>) this.context.getBean("springSessionSerializingConverter");
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(serializingConverter).isNotNull();
|
||||
Converter<Object, byte[]> sc =
|
||||
(Converter<Object, byte[]>) ReflectionTestUtils.getField(repository, "serializingConverter");
|
||||
assertThat(sc).isEqualTo(serializingConverter);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void customDeserializingConverterConfiguration() {
|
||||
registerAndRefresh(CustomDeserializingConverterConfiguration.class);
|
||||
|
||||
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
|
||||
Converter<byte[], Object> deserializingConverter =
|
||||
(Converter<byte[], Object>) this.context.getBean("springSessionDeserializingConverter");
|
||||
assertThat(repository).isNotNull();
|
||||
assertThat(deserializingConverter).isNotNull();
|
||||
Converter<byte[], Object> dc =
|
||||
(Converter<byte[], Object>) ReflectionTestUtils.getField(repository, "deserializingConverter");
|
||||
assertThat(dc).isEqualTo(deserializingConverter);
|
||||
}
|
||||
|
||||
private void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
this.context.register(annotatedClasses);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class EmptyConfiguration {
|
||||
}
|
||||
|
||||
static class BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class DefaultConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession(tableName = TABLE_NAME)
|
||||
static class CustomTableNameConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS)
|
||||
static class CustomMaxInactiveIntervalInSecondsConfiguration extends BaseConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class CustomLobHandlerConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
public LobHandler springSessionLobHandler() {
|
||||
return mock(LobHandler.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class CustomSerializingConverterConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
public Converter<Object, byte[]> springSessionSerializingConverter() {
|
||||
return mock(Converter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJdbcHttpSession
|
||||
static class CustomDeserializingConverterConfiguration extends BaseConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
public Converter<byte[], Object> springSessionDeserializingConverter() {
|
||||
return mock(Converter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user