Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions name.abuchen.portfolio.bootstrap/Application.e4xmi
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@
<children xsi:type="menu:MenuSeparator" xmi:id="_30NnAAtvEeW4ucTlYYYvbw" elementId="name.abuchen.portfolio.bootstrap.menuseparator.1"/>
<children xsi:type="menu:DirectMenuItem" xmi:id="_24C5sAtvEeW4ucTlYYYvbw" elementId="name.abuchen.portfolio.ui.menu.file.import.import-xml" label="%command.import.xml.import-xml" tooltip="" enabled="false"/>
<children xsi:type="menu:HandledMenuItem" xmi:id="_EbXUwAtwEeW4ucTlYYYvbw" elementId="name.abuchen.portfolio.ui.menu.file.import.ib" label="%command.import.xml.ib" tooltip="%command.import.xml.ib.tooltip" command="_QQsCcLCxEeefXq-wtldCQQ"/>
<children xsi:type="menu:Menu" xmi:id="_IBFlexWebServiceMenu" elementId="name.abuchen.portfolio.ui.menu.file.import.ib.webservice.menu" label="%command.import.xml.ib.webservice">
<children xsi:type="menu:HandledMenuItem" xmi:id="_IBFlexWebServiceImportMenuItem" elementId="name.abuchen.portfolio.ui.menu.file.import.ib.webservice.import" label="%command.import.xml.ib.webservice.import" command="_IBFlexWebServiceCommand"/>
<children xsi:type="menu:HandledMenuItem" xmi:id="_IBFlexWebServiceSettingsMenuItem" elementId="name.abuchen.portfolio.ui.menu.file.import.ib.webservice.settings" label="%command.import.xml.ib.webservice.settings" command="_IBFlexWebServiceSettingsCommand"/>
</children>
<children xsi:type="menu:MenuSeparator" xmi:id="_gNzZUPr7EeS4ucTlYYYvbw" elementId="name.abuchen.portfolio.bootstrap.menuseparator.2"/>
<children xsi:type="menu:HandledMenuItem" xmi:id="_EXYZ4IDUEfCu2LYuODFptg" elementId="name.abuchen.portfolio.ui.menu.file.importTaxonomies" label="%command.import.taxonomy" command="_zRHcgIDTEfCu2LYuODFptg"/>
</children>
Expand Down Expand Up @@ -182,6 +186,8 @@
<handlers xmi:id="_ftWRYLYaEeeT6Pb7tGT2uw" elementId="name.abuchen.portfolio.ui.handler.import.pdf" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.ImportPDFHandler" command="_pgC00EYREeSRXYVe298mXg"/>
<handlers xmi:id="_SL7BYLptEeSHOs-ah25CLg" elementId="name.abuchen.portfolio.ui.handler.import.pdf.create-text" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.CreateTextFromPDFHandler" command="_Jw7yELptEeSHOs-ah25CLg"/>
<handlers xmi:id="_LyQvULCxEeefXq-wtldCQQ" elementId="name.abuchen.portfolio.ui.handler.import.ib" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.ImportIBHandler" command="_QQsCcLCxEeefXq-wtldCQQ"/>
<handlers xmi:id="_IBFlexWebServiceHandler" elementId="name.abuchen.portfolio.ui.handler.import.ib.webservice" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.ImportIBFlexWebServiceHandler" command="_IBFlexWebServiceCommand"/>
<handlers xmi:id="_IBFlexWebServiceSettingsHandler" elementId="name.abuchen.portfolio.ui.handler.import.ib.webservice.settings" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.OpenIBFlexSettingsHandler" command="_IBFlexWebServiceSettingsCommand"/>
<handlers xmi:id="_Gig3QBiPEeOR1rNFCzC82A" elementId="name.abuchen.portfolio.ui.handler.consistencychecks" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.tools.RunConsistencyChecksHandler" command="_D9tYMBiPEeOR1rNFCzC82A"/>
<handlers xmi:id="_0fz04BiQEeOR1rNFCzC82A" elementId="name.abuchen.portfolio.ui.handler.updatequotes" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.UpdateQuotesHandler" command="_tukxoBiQEeOR1rNFCzC82A"/>
<handlers xmi:id="_88LHwBiREeOR1rNFCzC82A" elementId="name.abuchen.portfolio.ui.handler.updateproduct" contributionURI="bundleclass://name.abuchen.portfolio.ui/name.abuchen.portfolio.ui.handlers.UpdateHandler" command="_59FgcBiREeOR1rNFCzC82A"/>
Expand Down Expand Up @@ -273,6 +279,8 @@
<commands xmi:id="_pgC00EYREeSRXYVe298mXg" elementId="name.abuchen.portfolio.ui.command.import.pdf" commandName="%command.import.pdf.import-pdf"/>
<commands xmi:id="_Jw7yELptEeSHOs-ah25CLg" elementId="name.abuchen.portfolio.ui.command.import.pdf.create-text" commandName="importCommand.pdf.create-text"/>
<commands xmi:id="_QQsCcLCxEeefXq-wtldCQQ" elementId="name.abuchen.portfolio.ui.command.import.ib" commandName="importCommand.ib"/>
<commands xmi:id="_IBFlexWebServiceCommand" elementId="name.abuchen.portfolio.ui.command.import.ib.webservice" commandName="importCommand.ib.webservice"/>
<commands xmi:id="_IBFlexWebServiceSettingsCommand" elementId="name.abuchen.portfolio.ui.command.import.ib.webservice.settings" commandName="importCommand.ib.webservice.settings"/>
<commands xmi:id="_tukxoBiQEeOR1rNFCzC82A" elementId="name.abuchen.portfolio.ui.command.updatequotes" commandName="%command.updateQuotes.label">
<parameters xmi:id="_L7VRQApDEeqsXLK6ns2Lug" elementId="name.abuchen.portfolio.ui.param.filter" name="filter"/>
<parameters xmi:id="_WatchlistNameParam" elementId="name.abuchen.portfolio.ui.param.watchlist" name="watchlist"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ command.import.pdf.import-pdf = PDF Bank Documents
command.import.taxonomy = Import taxonomy\u2026
command.import.xml.ib = Interactive Brokers: Activity Flex Query
command.import.xml.ib.tooltip = XML with cash transactions, trades, corporate actions, ...
command.import.xml.ib.webservice = Interactive Brokers: Flex Web Service
command.import.xml.ib.webservice.import = Import\u2026
command.import.xml.ib.webservice.settings = Settings\u2026
command.import.xml.import-xml = XML Documents (experimental)
command.importWithCSVConfiguration.name = Templates
command.newFile.mnemonic = N
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ command.import.pdf.import-pdf = PDF-Bankdokumente
command.import.taxonomy = Klassifizierung importieren\u2026
command.import.xml.ib = Interactive Brokers: Kontoumsatz-Flex-Query
command.import.xml.ib.tooltip = XML mit Bartransaktionen, Trades, Kapitalma\u00DFnahmen, ...
command.import.xml.ib.webservice = Interactive Brokers: Flex Web Service
command.import.xml.ib.webservice.import = Importieren\u2026
command.import.xml.ib.webservice.settings = Einstellungen\u2026
command.import.xml.import-xml = XML-Dokumente (experimentell)
command.importWithCSVConfiguration.name = Vorlagen
command.newFile.mnemonic = N
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package name.abuchen.portfolio.datatransfer.ibflex;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;

import org.junit.Test;

import name.abuchen.portfolio.datatransfer.ibflex.IBFlexWebServiceClient.IBFlexException;

@SuppressWarnings("nls")
public class IBFlexWebServiceClientTest
{
// Sample XML responses from IB Flex Web Service
private static final String SUCCESS_RESPONSE = """
<?xml version="1.0" encoding="UTF-8"?>
<FlexStatementResponse timestamp="2024-01-15 10:30:00">
<Status>Success</Status>
<ReferenceCode>1234567890</ReferenceCode>
<Url>https://example.com</Url>
</FlexStatementResponse>
""";

private static final String ERROR_RESPONSE_TOKEN_INVALID = """
<?xml version="1.0" encoding="UTF-8"?>
<FlexStatementResponse timestamp="2024-01-15 10:30:00">
<Status>Fail</Status>
<ErrorCode>1003</ErrorCode>
<ErrorMessage>Token is invalid or has expired</ErrorMessage>
</FlexStatementResponse>
""";

private static final String ERROR_RESPONSE_STATEMENT_GENERATING = """
<?xml version="1.0" encoding="UTF-8"?>
<FlexStatementResponse timestamp="2024-01-15 10:30:00">
<Status>Fail</Status>
<ErrorCode>1019</ErrorCode>
<ErrorMessage>Statement generation in progress. Please try again shortly.</ErrorMessage>
</FlexStatementResponse>
""";

private static final String FLEX_STATEMENT = """
<?xml version="1.0" encoding="UTF-8"?>
<FlexQueryResponse queryName="Test Query" type="AF">
<FlexStatements count="1">
<FlexStatement accountId="U1234567" fromDate="2024-01-01" toDate="2024-01-15">
</FlexStatement>
</FlexStatements>
</FlexQueryResponse>
""";

@Test
public void testIsStatementGenerating()
{
IBFlexException statementGeneratingException = new IBFlexException(
IBFlexWebServiceClient.ERROR_STATEMENT_GENERATING, "Statement generation in progress");
assertTrue(statementGeneratingException.isStatementGenerating());

IBFlexException otherException = new IBFlexException(1001, "Invalid token");
assertFalse(otherException.isStatementGenerating());
}

@Test
public void testIsTokenExpired()
{
IBFlexException tokenExpiredException = new IBFlexException(IBFlexWebServiceClient.ERROR_TOKEN_INVALID,
"Token is invalid");
assertTrue(tokenExpiredException.isTokenExpired());

IBFlexException otherException = new IBFlexException(IBFlexWebServiceClient.ERROR_STATEMENT_GENERATING,
"Statement generation in progress");
assertFalse(otherException.isTokenExpired());
}

@Test
public void testExceptionWithCause()
{
RuntimeException cause = new RuntimeException("Network error");
IBFlexException exception = new IBFlexException("HTTP request failed", cause);

assertThat(exception.getMessage(), is("HTTP request failed"));
assertThat(exception.getCause(), is(cause));
assertThat(exception.getErrorCode(), is(-1));
assertFalse(exception.isStatementGenerating());
assertFalse(exception.isTokenExpired());
}

@Test
public void testParseReferenceCodeSuccess() throws IBFlexException, IOException
{
IBFlexWebServiceClient client = new IBFlexWebServiceClient();
String referenceCode = client.parseReferenceCode(SUCCESS_RESPONSE);
assertThat(referenceCode, is("1234567890"));
}

@Test
public void testParseReferenceCodeTokenInvalid()
{
IBFlexWebServiceClient client = new IBFlexWebServiceClient();
try
{
client.parseReferenceCode(ERROR_RESPONSE_TOKEN_INVALID);
fail("Expected IBFlexException");
}
catch (IBFlexException e)
{
assertThat(e.getErrorCode(), is(IBFlexWebServiceClient.ERROR_TOKEN_INVALID));
assertTrue(e.isTokenExpired());
assertThat(e.getMessage(), is("Token is invalid or has expired"));
}
catch (IOException e)
{
fail("Unexpected IOException: " + e.getMessage());
}
}

@Test
public void testParseErrorResponseStatementGenerating()
{
IBFlexWebServiceClient client = new IBFlexWebServiceClient();
try
{
client.parseErrorResponse(ERROR_RESPONSE_STATEMENT_GENERATING);
fail("Expected IBFlexException");
}
catch (IBFlexException e)
{
assertThat(e.getErrorCode(), is(IBFlexWebServiceClient.ERROR_STATEMENT_GENERATING));
assertTrue(e.isStatementGenerating());
}
catch (IOException e)
{
fail("Unexpected IOException: " + e.getMessage());
}
}

@Test
public void testFetchStatementSuccess() throws IBFlexException, IOException
{
// Mock HTTP executor that returns success response, then the actual statement
int[] callCount = { 0 };
IBFlexWebServiceClient client = new IBFlexWebServiceClient((host, path, token, queryOrRef, version) -> {
callCount[0]++;
if (path.contains("SendRequest"))
{
return SUCCESS_RESPONSE;
}
else
{
return FLEX_STATEMENT;
}
});

String result = client.fetchStatement("test-token", "12345");

assertThat(callCount[0], is(2)); // One for SendRequest, one for GetStatement
assertTrue(result.contains("FlexQueryResponse"));
}

@Test
public void testFetchStatementTokenExpired()
{
IBFlexWebServiceClient client = new IBFlexWebServiceClient(
(host, path, token, queryOrRef, version) -> ERROR_RESPONSE_TOKEN_INVALID);

try
{
client.fetchStatement("expired-token", "12345");
fail("Expected IBFlexException");
}
catch (IBFlexException e)
{
assertTrue(e.isTokenExpired());
}
catch (IOException e)
{
fail("Unexpected IOException: " + e.getMessage());
}
}

@Test
public void testFetchStatementNetworkError()
{
IBFlexWebServiceClient client = new IBFlexWebServiceClient((host, path, token, queryOrRef, version) -> {
throw new IOException("Network unreachable");
});

try
{
client.fetchStatement("test-token", "12345");
fail("Expected IOException");
}
catch (IBFlexException e)
{
fail("Unexpected IBFlexException: " + e.getMessage());
}
catch (IOException e)
{
assertThat(e.getMessage(), is("Network unreachable"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package name.abuchen.portfolio.ui.dialogs;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

import java.time.LocalDateTime;

import org.junit.Before;
import org.junit.Test;

import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.ui.dialogs.IBFlexConfigurationDialog.IBFlexModel;

public class IBFlexModelTest
{
private Client client;

@Before
public void setUp()
{
client = new Client();
}

@Test
public void testModelReadsStoredValues()
{
client.setProperty("ibflex-token", "mytoken");
client.setProperty("ibflex-query-id", "12345");

IBFlexModel model = new IBFlexModel(client);

assertThat(model.getToken(), is("mytoken"));
assertThat(model.getQueryId(), is("12345"));
}

@Test
public void testApplyChangesStoresAndTrimsValues()
{
IBFlexModel model = new IBFlexModel(client);
model.setToken(" newtoken ");
model.setQueryId(" 67890 ");

model.applyChanges();

assertThat(IBFlexModel.getToken(client), is("newtoken"));
assertThat(IBFlexModel.getQueryId(client), is("67890"));
}

@Test
public void testApplyChangesRemovesBlankValues()
{
client.setProperty("ibflex-token", "existing");
client.setProperty("ibflex-query-id", "existing");

IBFlexModel model = new IBFlexModel(client);
model.setToken(" ");
model.setQueryId("");

model.applyChanges();

assertThat(IBFlexModel.getToken(client), is(nullValue()));
assertThat(IBFlexModel.getQueryId(client), is(nullValue()));
}

@Test
public void testClearConfiguration()
{
client.setProperty("ibflex-token", "mytoken");
client.setProperty("ibflex-query-id", "12345");
IBFlexModel.setLastImportDate(client, LocalDateTime.of(2024, 1, 15, 10, 30));

IBFlexModel.clearConfiguration(client);

assertThat(IBFlexModel.getToken(client), is(nullValue()));
assertThat(IBFlexModel.getQueryId(client), is(nullValue()));
assertThat(IBFlexModel.getLastImportDate(client), is(nullValue()));
}

@Test
public void testHasConfiguration()
{
assertThat(IBFlexModel.hasConfiguration(client), is(false));

client.setProperty("ibflex-token", "mytoken");
client.setProperty("ibflex-query-id", "12345");

assertThat(IBFlexModel.hasConfiguration(client), is(true));
}

@Test
public void testHasConfigurationIgnoresBlankValues()
{
client.setProperty("ibflex-token", " ");
client.setProperty("ibflex-query-id", "");

assertThat(IBFlexModel.hasConfiguration(client), is(false));
}

@Test
public void testGetLastImportDateReturnsNullWhenNotSet()
{
assertThat(IBFlexModel.getLastImportDate(client), is(nullValue()));
}

@Test
public void testSetAndGetLastImportDate()
{
LocalDateTime date = LocalDateTime.of(2024, 6, 15, 14, 30, 45);

IBFlexModel.setLastImportDate(client, date);

assertThat(IBFlexModel.getLastImportDate(client), is(date));
}

@Test
public void testSetLastImportDateToNullRemovesProperty()
{
IBFlexModel.setLastImportDate(client, LocalDateTime.of(2024, 1, 1, 0, 0));

IBFlexModel.setLastImportDate(client, null);

assertThat(IBFlexModel.getLastImportDate(client), is(nullValue()));
}
}
Loading
Loading