Skip to content

Commit

Permalink
Increase test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
esanchezros committed Oct 27, 2024
1 parent 88ae5f0 commit ed19789
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 61 deletions.
127 changes: 66 additions & 61 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,67 +15,6 @@ It simplifies the configuration required to create and start an https://www.quic

In this Spring Boot Starter, QuickFIX/J Server is equivalent to `Acceptor` and QuickFIX/J Client to `Initiator`.

== Breaking changes with previous versions

Due to the changes in https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[how dependencies are autowired from Spring Framework 6.1] and https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#parameter-name-discovery[Parameter Name Discovery], having the quickfixj client and server in the same spring context is not supported.

If you have an application that need to use both, or multiple of one, you can use the following approach to separate the Spring contexts and configurations:

[source,java]
----
@SpringBootConfiguration
public class ApplicationConfiguration {
public static void main(String[] args) {
SpringApplicationBuilder parent =
new SpringApplicationBuilder(QuickFixJCommonConfiguration.class);
parent.child(QuickFixJServerYourCompanyContextConfiguration.class).web(WebApplicationType.SERVLET);
parent.child(QuickFixJClientNDAQContextConfiguration.class).web(WebApplicationType.SERVLET);
parent.child(QuickFixJClientFOREXContextConfiguration.class).web(WebApplicationType.SERVLET);
parent.child(QuickFixJClientDOWJONESContextConfiguration.class).web(WebApplicationType.SERVLET);
parent.run(args);
}
@Configuration
public class QuickFixJCommonConfiguration {
@Bean
public QuickFixJTemplate quickFixJTemplate() {
return new QuickFixJTemplate();
}
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@PropertySource("classpath:server/server.properties")
public class QuickFixJServerYourCompanyContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@PropertySource("classpath:client/ndaq.properties")
public class QuickFixJClientNDAQContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@PropertySource("classpath:client/forex.properties")
public class QuickFixJClientFOREXContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration(proxyBeanMethods = false)
@PropertySource("classpath:client/dow-jones.properties")
public class QuickFixJClientDOWJONESContextConfiguration {
// override beans if required
}
}
----

== Getting started

To use the QuickFIX/J Server or QuickFIX/J Client you need to add the QuickFIX/J Spring Boot Starter dependency in your project.
Expand Down Expand Up @@ -128,6 +67,72 @@ and add the specific version(s) of the FIX messages you need in your project `de
</dependency>
----

== Breaking changes with previous versions

Due to the changes in https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-6.x#core-container[how dependencies are autowired from Spring Framework 6.1] and https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#parameter-name-discovery[Parameter Name Discovery], having the quickfixj client and server in the same spring context is not supported.

If you have an application that need to use both, or multiple of one, you can use the following approach to separate the Spring contexts and configurations:

[source,java]
----
public class ApplicationConfiguration {
public static void main(String[] args) {
SpringApplicationBuilder parentBuilder
= new SpringApplicationBuilder(QuickFixJCommonConfiguration.class)
.web(WebApplicationType.NONE);
parentBuilder.run(args);
parentBuilder.child(QuickFixJServerYourCompanyContextConfiguration.class)
.properties("spring.config.name=server")
.web(WebApplicationType.SERVLET).run(args);
parentBuilder.child(QuickFixJClientNDAQContextConfiguration.class)
.properties("spring.config.name=ndaq")
.web(WebApplicationType.SERVLET).run(args);
parentBuilder.child(QuickFixJClientFOREXContextConfiguration.class)
.properties("spring.config.name=forex")
.web(WebApplicationType.SERVLET).run(args);
parentBuilder.child(QuickFixJClientDOWJONESContextConfiguration.class)
.properties("spring.config.name=dow-jones")
.web(WebApplicationType.SERVLET).run(args);
}
@Configuration
public class QuickFixJCommonConfiguration {
@Bean
public QuickFixJTemplate quickFixJTemplate() {
return new QuickFixJTemplate();
}
}
@EnableAutoConfiguration
@Configuration
public class QuickFixJServerYourCompanyContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration
public class QuickFixJClientNDAQContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration
public class QuickFixJClientFOREXContextConfiguration {
// override beans if required
}
@EnableAutoConfiguration
@Configuration
public class QuickFixJClientDOWJONESContextConfiguration {
// override beans if required
}
}
----

== QuickFIX/J Spring Boot Starter - Server (Acceptor)

=== Enabling QuickFix/J Server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Optional;

import static java.util.Optional.empty;
import static org.apache.commons.lang3.StringUtils.isBlank;

/**
* {@link SessionSettings} helper class that attempts to load the settings files from the default locations
Expand Down Expand Up @@ -66,6 +67,9 @@ public SessionSettings loadSettings(String... locations) {
}

public SessionSettings loadSettingsFromString(String configString) {
if (isBlank(configString)) {
throw new SettingsNotFoundException("configString is blank or empty");
}
try {
return new SessionSettings(new ByteArrayInputStream(configString.getBytes()));
} catch (RuntimeException | ConfigError e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -116,4 +119,81 @@ public void shouldThrowConfigurationExceptionUponRuntimeErrorFailure() throws Ex

verify(connector).start();
}

@Test
void testIsAutoStartup_defaultTrue() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// By default autoStartup should be true
assertTrue(connectorManager.isAutoStartup(), "The autoStartup should be true by default");
}

@Test
void testIsAutoStartup_setToFalse() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// Set autoStartup to false
connectorManager.setAutoStartup(false);
assertFalse(connectorManager.isAutoStartup(), "The autoStartup should be false after setting it");
}

@Test
void testIsAutoStartup_setToTrue() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);

// First set it to false
connectorManager.setAutoStartup(false);
assertFalse(connectorManager.isAutoStartup(), "The autoStartup should be false after setting it");

// Then set it back to true
connectorManager.setAutoStartup(true);
assertTrue(connectorManager.isAutoStartup(), "The autoStartup should be true after setting it back");
}

@Test
void testGetPhase_defaultValue() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// By default the phase should be Integer.MAX_VALUE
assertEquals(Integer.MAX_VALUE, connectorManager.getPhase(), "The default phase should be Integer.MAX_VALUE");
}

@Test
void testSetPhase_customValue() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// Set phase to a specific value and check
int customPhase = 42;
connectorManager.setPhase(customPhase);
assertEquals(customPhase, connectorManager.getPhase(), "The phase should be the custom value set");
}

@Test
void testIsForceDisconnect_defaultValue() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// By default, forceDisconnect should be false
assertFalse(connectorManager.isForceDisconnect(), "The default forceDisconnect should be false");
}

@Test
void testSetForceDisconnect_true() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// Set forceDisconnect to true and check
connectorManager.setForceDisconnect(true);
assertTrue(connectorManager.isForceDisconnect(), "The forceDisconnect should be true after setting it");
}

@Test
void testSetForceDisconnect_false() {
Connector connector = mock(Connector.class);
ConnectorManager connectorManager = new ConnectorManager(connector);
// Initially set forceDisconnect to true
connectorManager.setForceDisconnect(true);
// Set forceDisconnect to false and check
connectorManager.setForceDisconnect(false);
assertFalse(connectorManager.isForceDisconnect(), "The forceDisconnect should be false after setting it back");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@
import io.allune.quickfixj.spring.boot.starter.exception.SettingsNotFoundException;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.DefaultResourceLoader;
import quickfix.ConfigError;
import quickfix.FieldConvertError;
import quickfix.SessionID;
import quickfix.SessionSettings;

import java.io.File;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* @author Eduardo Sanchez-Ros
Expand All @@ -50,4 +56,48 @@ public void shouldThrowSettingsNotFoundExceptionIfNoneFound() {
assertThatThrownBy(() -> sessionSettingsLocator.loadSettings(null, null, null, null))
.isInstanceOf(SettingsNotFoundException.class);
}

@Test
void testLoadSettingsFromString_validConfig() throws ConfigError, FieldConvertError {
String configString = "[DEFAULT]\n" +
"ConnectionType=initiator\n" +
"BeginString=FIX.4.2\n" +
"SenderCompID=CLIENT1\n" +
"TargetCompID=SERVER\n" +
"\n" +
"[SESSION]\n" +
"SocketConnectPort=5001\n" +
"SocketConnectHost=localhost\n";

SessionSettingsLocator sessionSettingsLocator = new SessionSettingsLocator(new DefaultResourceLoader());
SessionSettings settings = sessionSettingsLocator.loadSettingsFromString(configString);

assertThat("initiator").isEqualTo(settings.getString("ConnectionType"));
assertThat("FIX.4.2").isEqualTo(settings.getString("BeginString"));
assertThat("CLIENT1").isEqualTo(settings.getString("SenderCompID"));
assertThat("SERVER").isEqualTo(settings.getString("TargetCompID"));

SessionID sessionID = new SessionID("FIX.4.2", "CLIENT1", "SERVER");
assertThat(5001).isEqualTo(settings.getLong(sessionID, "SocketConnectPort"));
assertThat("localhost").isEqualTo(settings.getString(sessionID, "SocketConnectHost"));
}

@Test
void testLoadSettingsFromString_emptyConfig() {
String configString = "";
SessionSettingsLocator sessionSettingsLocator = new SessionSettingsLocator(new DefaultResourceLoader());
assertThatThrownBy(() -> sessionSettingsLocator.loadSettingsFromString(configString))
.isInstanceOf(SettingsNotFoundException.class);
}

@Test
void testLoadSettingsFromString_invalidConfig() {
String configString = "[INVALID]\n" +
"ThisIsNotAValidSetting";

SessionSettingsLocator sessionSettingsLocator = new SessionSettingsLocator(new DefaultResourceLoader());
assertDoesNotThrow(() -> {
sessionSettingsLocator.loadSettingsFromString(configString);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import quickfix.Message;
import quickfix.Session;
import quickfix.SessionID;
import quickfix.field.ApplVerID;
import quickfix.field.BeginString;
import quickfix.field.SenderCompID;
import quickfix.field.TargetCompID;
Expand Down Expand Up @@ -339,6 +340,28 @@ public void shouldNotThrowMessageValidationExceptionGivenValidationIsDisabled()
verify(applicationDataDictionary, never()).validate(any());
}

@Test
public void shouldDoValidationForSpecificVersion() throws FieldNotFound, IncorrectTagValue, IncorrectDataFormat {
// Given
Message.Header header = mock(Message.Header.class);
given(message.getHeader()).willReturn(header);
given(header.getString(ApplVerID.FIELD)).willReturn(ApplVerID.FIX43);
given(header.getString(SenderCompID.FIELD)).willReturn(expectedSender);
given(header.getString(TargetCompID.FIELD)).willReturn(expectedTarget);
given(header.getString(BeginString.FIELD)).willReturn(expectedBeginString);
given(sessionLookupHandler.lookupBySessionID(any())).willReturn(session);
given(session.getDataDictionaryProvider()).willReturn(dataDictionaryProvider);
given(dataDictionaryProvider.getApplicationDataDictionary(new ApplVerID(ApplVerID.FIX43))).willReturn(applicationDataDictionary);
quickFixJTemplate.setDoValidation(true);

// When/Then
assertThatCode(() -> quickFixJTemplate.send(message)).doesNotThrowAnyException();

verify(session).getDataDictionaryProvider();
verify(dataDictionaryProvider).getApplicationDataDictionary(any());
verify(applicationDataDictionary).validate(any(), any(Boolean.class));
}

private void assertSessionID(SessionID expectedSessionID) {
ArgumentCaptor<SessionID> sessionIDCaptor = ArgumentCaptor.forClass(SessionID.class);
verify(sessionLookupHandler).lookupBySessionID(sessionIDCaptor.capture());
Expand Down

0 comments on commit ed19789

Please sign in to comment.