Skip to content

Commit a03dd7d

Browse files
Nirus2000buchen
authored andcommitted
Add JUnit matcher for skippedItem + helper methods to skip or fail transaction
Extractor.java Added originalItem field to SkippedItem with getOriginalItem() getter to allow inspection of the wrapped transaction PDFParser.java After calling the wrap callback, automatically sets failureMessage from transaction context if not already set After calling the wrap callback, automatically wraps the item in a SkippedItem if the transaction context is marked accordingly (only if the callback did not already return a SkippedItem) AbstractPDFExtractor.java Added skipTransaction(DocumentType, String) helper to signal that a transaction should be skipped Added failTransaction(ParsedData, String) helper to signal that a transaction has failed Added source propagation for SkippedItem.originalItem OnvistaPDFExtractor.java Removed private SKIP_TRANSACTION constant and FAILURE usages — now using skipTransaction() and failTransaction() helpers Removed inline SKIP_TRANSACTION and FAILURE checks from wrap callbacks — now handled transparently by PDFParser Simplified wrap callbacks from (t, ctx) -> to t -> / TransactionItem::new Fixed testDividende18: replaced incorrect withFailureMessage(..., dividend(...)) with skippedItem(..., dividend(...)) ExtractorMatchers.java Added SkippedItemMatcher with optional inner matcher support Added skippedItem(String) and skippedItem(String, Matcher) factory methods ExtractorMatchersTest.java Added tests for hasExDate (failure + success) Added tests for skippedItem (not skipped, wrong reason, correct reason, with inner matcher failure + success) Issue: portfolio-performance#5521
1 parent 966bdc5 commit a03dd7d

File tree

7 files changed

+307
-144
lines changed

7 files changed

+307
-144
lines changed

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ExtractorMatchers.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import name.abuchen.portfolio.datatransfer.Extractor.Item;
1818
import name.abuchen.portfolio.datatransfer.Extractor.PortfolioTransferItem;
1919
import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem;
20+
import name.abuchen.portfolio.datatransfer.Extractor.SkippedItem;
2021
import name.abuchen.portfolio.datatransfer.Extractor.TransactionItem;
2122
import name.abuchen.portfolio.model.Account;
2223
import name.abuchen.portfolio.model.AccountTransaction;
@@ -228,6 +229,69 @@ public static Matcher<Extractor.Item> withFailureMessage(String message, Matcher
228229
return new FailureMessageItemMatcher(message, matcher);
229230
}
230231

232+
private static class SkippedItemMatcher extends TypeSafeDiagnosingMatcher<Extractor.Item>
233+
{
234+
private String skipReason;
235+
private Matcher<Extractor.Item> matcher;
236+
237+
public SkippedItemMatcher(String skipReason, Matcher<Extractor.Item> matcher)
238+
{
239+
this.skipReason = skipReason;
240+
this.matcher = matcher;
241+
}
242+
243+
@Override
244+
protected boolean matchesSafely(Extractor.Item item, Description mismatchDescription)
245+
{
246+
if (!item.isSkipped())
247+
{
248+
mismatchDescription.appendText("\n* item is not skipped"); //$NON-NLS-1$
249+
return false;
250+
}
251+
252+
var skipped = (SkippedItem) item;
253+
if (!Objects.equals(skipReason, skipped.getSkipReason()))
254+
{
255+
mismatchDescription.appendText(
256+
MessageFormat.format("\n* skip reason is ''{0}''", skipped.getSkipReason())); //$NON-NLS-1$
257+
return false;
258+
}
259+
260+
if (matcher != null)
261+
{
262+
var original = skipped.getOriginalItem();
263+
if (!matcher.matches(original))
264+
{
265+
matcher.describeMismatch(original, mismatchDescription);
266+
return false;
267+
}
268+
}
269+
270+
return true;
271+
}
272+
273+
@Override
274+
public void describeTo(Description description)
275+
{
276+
description.appendText(MessageFormat.format("a skipped item with reason ''{0}''", skipReason)); //$NON-NLS-1$
277+
if (matcher != null)
278+
{
279+
description.appendText(" and "); //$NON-NLS-1$
280+
matcher.describeTo(description);
281+
}
282+
}
283+
}
284+
285+
public static Matcher<Extractor.Item> skippedItem(String skipReason)
286+
{
287+
return new SkippedItemMatcher(skipReason, null);
288+
}
289+
290+
public static Matcher<Extractor.Item> skippedItem(String skipReason, Matcher<Extractor.Item> matcher)
291+
{
292+
return new SkippedItemMatcher(skipReason, matcher);
293+
}
294+
231295
public static Matcher<Extractor.Item> hasAccount(Account account)
232296
{
233297
return new PropertyMatcher<>("primary account", account, Item::getAccountPrimary); //$NON-NLS-1$

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ExtractorMatchersTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.dividend;
55
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasAmount;
66
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasCurrencyCode;
7+
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasExDate;
78
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasFees;
89
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasForexGrossValue;
910
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasGrossValue;
@@ -15,6 +16,7 @@
1516
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasTicker;
1617
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.hasWkn;
1718
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
19+
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.skippedItem;
1820
import static org.hamcrest.CoreMatchers.hasItem;
1921
import static org.hamcrest.MatcherAssert.assertThat;
2022

@@ -28,6 +30,7 @@
2830
import org.junit.Test;
2931

3032
import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem;
33+
import name.abuchen.portfolio.datatransfer.Extractor.SkippedItem;
3134
import name.abuchen.portfolio.datatransfer.Extractor.TransactionItem;
3235
import name.abuchen.portfolio.model.AccountTransaction;
3336
import name.abuchen.portfolio.model.Security;
@@ -41,6 +44,7 @@ public class ExtractorMatchersTest
4144
{
4245
private static SecurityItem someSecurity;
4346
private static TransactionItem someTransactionItem;
47+
private static SkippedItem someSkippedItem;
4448

4549
@BeforeClass
4650
public static void setup()
@@ -65,9 +69,12 @@ public static void setup()
6569
tx.setNote("test");
6670
tx.setShares(Values.Share.factorize(123));
6771
tx.setDateTime(LocalDateTime.parse("2023-04-30T12:45"));
72+
tx.setExDate(LocalDateTime.parse("2023-04-28T00:00"));
6873
tx.setSecurity(s);
6974

7075
someTransactionItem = new TransactionItem(tx);
76+
77+
someSkippedItem = new SkippedItem(someTransactionItem, "skip reason");
7178
}
7279

7380
@Test(expected = AssertionError.class)
@@ -259,4 +266,46 @@ public void testCurrencyCodeSuccess()
259266
assertThat(List.of(someSecurity), hasItem(security(hasCurrencyCode("USD"))));
260267
}
261268

269+
@Test(expected = AssertionError.class)
270+
public void testExDateFailure()
271+
{
272+
assertThat(List.of(someTransactionItem), hasItem(dividend(hasExDate("2023-01-01"))));
273+
}
274+
275+
@Test
276+
public void testExDateSuccess()
277+
{
278+
assertThat(List.of(someTransactionItem), hasItem(dividend(hasExDate("2023-04-28"))));
279+
}
280+
281+
@Test(expected = AssertionError.class)
282+
public void testSkippedItemNotSkipped()
283+
{
284+
assertThat(List.of(someTransactionItem), hasItem(skippedItem("skip reason")));
285+
}
286+
287+
@Test(expected = AssertionError.class)
288+
public void testSkippedItemWrongReasonFailure()
289+
{
290+
assertThat(List.of(someSkippedItem), hasItem(skippedItem("wrong reason")));
291+
}
292+
293+
@Test
294+
public void testSkippedItemReasonSuccess()
295+
{
296+
assertThat(List.of(someSkippedItem), hasItem(skippedItem("skip reason")));
297+
}
298+
299+
@Test(expected = AssertionError.class)
300+
public void testSkippedItemWithInnerMatcherFailure()
301+
{
302+
assertThat(List.of(someSkippedItem), hasItem(skippedItem("skip reason", dividend(hasAmount("EUR", 999)))));
303+
}
304+
305+
@Test
306+
public void testSkippedItemWithInnerMatcherSuccess()
307+
{
308+
assertThat(List.of(someSkippedItem), hasItem(skippedItem("skip reason", dividend(hasAmount("EUR", 100)))));
309+
}
310+
262311
}

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/onvista/OnvistaPDFExtractorTest.java

Lines changed: 47 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.removal;
2828
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.sale;
2929
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.security;
30+
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.skippedItem;
3031
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.taxes;
3132
import static name.abuchen.portfolio.datatransfer.ExtractorMatchers.withFailureMessage;
3233
import static name.abuchen.portfolio.datatransfer.ExtractorTestUtilities.countAccountTransactions;
@@ -3143,7 +3144,7 @@ public void testDividende16()
31433144
assertThat(security.getName(), is("Garmin Ltd. Namens-Aktien SF 0,10"));
31443145
assertThat(security.getCurrencyCode(), is("USD"));
31453146

3146-
// check dividends tax transaction
3147+
// check dividends transaction
31473148
var transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).findFirst()
31483149
.orElseThrow(IllegalArgumentException::new).getSubject();
31493150

@@ -3240,8 +3241,8 @@ public void testDividende17()
32403241

32413242
// check dividends transaction
32423243
assertThat(results, hasItem(dividend( //
3243-
hasDate("2019-09-09"), hasShares(60), //
3244-
hasExDate("2019-08-30"), //
3244+
hasDate("2019-09-09"), hasExDate("2019-08-30"), //
3245+
hasShares(60.00), //
32453246
hasSource("Dividende17.txt"), //
32463247
hasNote("Abrechnungs-Nr. 26128781"), //
32473248
hasAmount("EUR", 7.65), hasGrossValue("EUR", 15.73), //
@@ -3276,18 +3277,12 @@ public void testDividende17WithSecurityInEUR()
32763277

32773278
// check dividends transaction
32783279
assertThat(results, hasItem(dividend( //
3279-
hasDate("2019-09-09"), hasShares(60), //
3280-
hasExDate("2019-08-30"), //
3281-
hasSource("Dividende17.txt"), hasNote("Abrechnungs-Nr. 26128781"), //
3280+
hasDate("2019-09-09"), hasExDate("2019-08-30"), //
3281+
hasShares(60.00), //
3282+
hasSource("Dividende17.txt"), //
3283+
hasNote("Abrechnungs-Nr. 26128781"), //
32823284
hasAmount("EUR", 7.65), hasGrossValue("EUR", 15.73), //
3283-
hasTaxes("EUR", (39.00 / 9.9148) + 3.93 + 0.22), hasFees("EUR", 0.00), //
3284-
check(tx -> {
3285-
var c = new CheckCurrenciesAction();
3286-
var account = new Account();
3287-
account.setCurrencyCode("EUR");
3288-
var s = c.process((AccountTransaction) tx, account);
3289-
assertThat(s, is(Status.OK_STATUS));
3290-
}))));
3285+
hasTaxes("EUR", (39.00 / 9.9148) + 3.93 + 0.22), hasFees("EUR", 0.00))));
32913286
}
32923287

32933288
@Test
@@ -3317,12 +3312,23 @@ public void testDividende18()
33173312

33183313
// check dividends transaction
33193314
assertThat(results, hasItem(dividend( //
3320-
hasDate("2015-12-17T00:00"), hasShares(156.729), //
3321-
hasExDate("2015-12-15"), //
3315+
hasDate("2015-12-17T00:00"), hasExDate("2015-12-15"), //
3316+
hasShares(156.729), //
33223317
hasSource("Dividende18.txt"), //
33233318
hasNote("Abrechnungs-Nr. 70187215 | Ertrag für 2014/15"), //
33243319
hasAmount("EUR", 7.68), hasGrossValue("EUR", 11.84), //
33253320
hasTaxes("EUR", 4.16), hasFees("EUR", 0.00))));
3321+
3322+
// check skipped transaction
3323+
assertThat(results, hasItem(skippedItem( //
3324+
Messages.MsgErrorTransactionTypeNotSupportedOrRequired, //
3325+
dividend( //
3326+
hasDate("2015-12-17T00:00"), hasExDate("2015-12-15"), //
3327+
hasShares(156.729), //
3328+
hasSource("Dividende18.txt"), //
3329+
hasNote("Ausführungs-Nr. 33217061"), //
3330+
hasAmount("EUR", 3.01), hasGrossValue("EUR", 3.01), //
3331+
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))));
33263332
}
33273333

33283334
@Test
@@ -3674,12 +3680,10 @@ public void testVorabpauschale02()
36743680
assertThat(errors, empty());
36753681
assertThat(countSecurities(results), is(2L));
36763682
assertThat(countBuySell(results), is(0L));
3677-
assertThat(countAccountTransactions(results), is(2L));
3683+
assertThat(countAccountTransactions(results), is(0L));
36783684
assertThat(countAccountTransfers(results), is(0L));
3679-
assertThat(countItemsWithFailureMessage(results), is(2L));
3680-
assertThat(countSkippedItems(results), is(0L));
3685+
36813686
assertThat(results.size(), is(4));
3682-
new AssertImportActions().check(results, "EUR");
36833687

36843688
// check security
36853689
var security = results.stream().filter(SecurityItem.class::isInstance).findFirst()
@@ -3690,29 +3694,15 @@ public void testVorabpauschale02()
36903694
assertThat(security.getName(), is("iShs Core MSCI EM IMI U.ETF Registered Shares o.N."));
36913695
assertThat(security.getCurrencyCode(), is("EUR"));
36923696

3693-
// check cancellation (Storno) transaction
3694-
var cancellation = (TransactionItem) results.stream() //
3695-
.filter(i -> i.isFailure()) //
3696-
.filter(TransactionItem.class::isInstance) //
3697-
.findFirst().orElseThrow(IllegalArgumentException::new);
3698-
3699-
assertThat(((AccountTransaction) cancellation.getSubject()).getType(), is(AccountTransaction.Type.TAXES));
3700-
assertThat(cancellation.getFailureMessage(), is(Messages.MsgErrorTransactionTypeNotSupportedOrRequired));
3701-
3702-
assertThat(((Transaction) cancellation.getSubject()).getDateTime(),
3703-
is(LocalDateTime.parse("2020-01-02T00:00")));
3704-
assertThat(((Transaction) cancellation.getSubject()).getShares(), is(Values.Share.factorize(100)));
3705-
assertThat(((Transaction) cancellation.getSubject()).getSource(), is("Vorabpauschale02.txt"));
3706-
assertThat(((Transaction) cancellation.getSubject()).getNote(), is("Ausführungs-Nr. 82128903"));
3707-
3708-
assertThat(((Transaction) cancellation.getSubject()).getMonetaryAmount(),
3709-
is(Money.of("EUR", Values.Amount.factorize(0.00))));
3710-
assertThat(((Transaction) cancellation.getSubject()).getGrossValue(),
3711-
is(Money.of("EUR", Values.Amount.factorize(0.00))));
3712-
assertThat(((Transaction) cancellation.getSubject()).getUnitSum(Unit.Type.TAX),
3713-
is(Money.of("EUR", Values.Amount.factorize(0.00))));
3714-
assertThat(((Transaction) cancellation.getSubject()).getUnitSum(Unit.Type.FEE),
3715-
is(Money.of("EUR", Values.Amount.factorize(0.00))));
3697+
// check cancellation (Amount = 0,00) transaction
3698+
assertThat(results, hasItem(skippedItem( //
3699+
Messages.MsgErrorTransactionTypeNotSupportedOrRequired, //
3700+
taxes( //
3701+
hasDate("2020-01-02"), hasShares(100.00), //
3702+
hasSource("Vorabpauschale02.txt"), //
3703+
hasNote("Ausführungs-Nr. 82128903"), //
3704+
hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), //
3705+
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))));
37163706
}
37173707

37183708
@Test
@@ -3727,12 +3717,10 @@ public void testVorabpauschale03()
37273717
assertThat(errors, empty());
37283718
assertThat(countSecurities(results), is(2L));
37293719
assertThat(countBuySell(results), is(0L));
3730-
assertThat(countAccountTransactions(results), is(2L));
3720+
assertThat(countAccountTransactions(results), is(0L));
37313721
assertThat(countAccountTransfers(results), is(0L));
3732-
assertThat(countItemsWithFailureMessage(results), is(2L));
3733-
assertThat(countSkippedItems(results), is(0L));
3722+
37343723
assertThat(results.size(), is(4));
3735-
new AssertImportActions().check(results, "EUR");
37363724

37373725
// check security
37383726
assertThat(results, hasItem(security( //
@@ -3746,20 +3734,22 @@ public void testVorabpauschale03()
37463734
hasCurrencyCode("EUR"))));
37473735

37483736
// check cancellation (Amount = 0,00) transaction
3749-
assertThat(results, hasItem(withFailureMessage( //
3737+
assertThat(results, hasItem(skippedItem( //
37503738
Messages.MsgErrorTransactionTypeNotSupportedOrRequired, //
37513739
taxes( //
37523740
hasDate("2021-01-04"), hasShares(0.1), //
3753-
hasSource("Vorabpauschale03.txt"), hasNote("Ausführungs-Nr. 66023908"), //
3741+
hasSource("Vorabpauschale03.txt"), //
3742+
hasNote("Ausführungs-Nr. 66023908"), //
37543743
hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), //
37553744
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))));
37563745

37573746
// check cancellation (Amount = 0,00) transaction
3758-
assertThat(results, hasItem(withFailureMessage( //
3747+
assertThat(results, hasItem(skippedItem( //
37593748
Messages.MsgErrorTransactionTypeNotSupportedOrRequired, //
37603749
taxes( //
37613750
hasDate("2021-01-04"), hasShares(0.1), //
3762-
hasSource("Vorabpauschale03.txt"), hasNote("Ausführungs-Nr. 55108371"), //
3751+
hasSource("Vorabpauschale03.txt"), //
3752+
hasNote("Ausführungs-Nr. 55108371"), //
37633753
hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), //
37643754
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))));
37653755
}
@@ -3776,12 +3766,11 @@ public void testVorabpauschale04()
37763766
assertThat(errors, empty());
37773767
assertThat(countSecurities(results), is(1L));
37783768
assertThat(countBuySell(results), is(0L));
3779-
assertThat(countAccountTransactions(results), is(1L));
3769+
assertThat(countAccountTransactions(results), is(0L));
37803770
assertThat(countAccountTransfers(results), is(0L));
3781-
assertThat(countItemsWithFailureMessage(results), is(1L));
3782-
assertThat(countSkippedItems(results), is(0L));
3771+
assertThat(countItemsWithFailureMessage(results), is(0L));
3772+
assertThat(countSkippedItems(results), is(1L));
37833773
assertThat(results.size(), is(2));
3784-
new AssertImportActions().check(results, "EUR");
37853774

37863775
// check security
37873776
assertThat(results, hasItem(security( //
@@ -3790,11 +3779,12 @@ public void testVorabpauschale04()
37903779
hasCurrencyCode("EUR"))));
37913780

37923781
// check cancellation (Amount = 0,00) transaction
3793-
assertThat(results, hasItem(withFailureMessage( //
3782+
assertThat(results, hasItem(skippedItem( //
37943783
Messages.MsgErrorTransactionTypeNotSupportedOrRequired, //
37953784
taxes( //
37963785
hasDate("2020-01-02"), hasShares(171.6149), //
3797-
hasSource("Vorabpauschale04.txt"), hasNote("Ausführungs-Nr. 66023908"), //
3786+
hasSource("Vorabpauschale04.txt"), //
3787+
hasNote("Ausführungs-Nr. 66023908"), //
37983788
hasAmount("EUR", 0.00), hasGrossValue("EUR", 0.00), //
37993789
hasTaxes("EUR", 0.00), hasFees("EUR", 0.00)))));
38003790
}

0 commit comments

Comments
 (0)