Verified Commit 5d689433 authored by 35V LG84's avatar 35V LG84

Reject negative unit costs and prices

Reject semantically wrong negative commodity positions:

 * unit costs, e.g.  `{ -2 € }`
 * unit prices, e.g. `@ -2 €`
Signed-off-by: 35V LG84's avatar35V LG84 <35vlg84-x4e6b92@e257.fi>
parent a7e1d2d5
Pipeline #64310848 passed with stage
in 11 minutes and 36 seconds
......@@ -23,7 +23,9 @@ New features and changes in this release:
==== Fixes
Fixes in this release: None
Fixes in this release:
* Reject semantically wrong negative unit costs (e.g. `{ -2 € }`) and unit prices (e.g. `@ -2 €`)
==== Development
......
......@@ -159,6 +159,17 @@ abstract class CtxHandler {
Option(u.opt_position()).fold({
(postAmount, false)
}) { pos =>
Option(pos.opt_opening_pos()).foreach(opening_pos => {
val opening_price = handleAmount(opening_pos.amount())
if (opening_price < 0) {
val lineNro = postingCtx.start.getLine
val msg = "Error on line: " + lineNro.toString +
"; Unit cost '{ ... }' is negative"
log.error(msg)
throw new CommodityException(msg)
}
})
Option(pos.closing_pos()).fold({
// plain value, no closing position
(postAmount, false)
......@@ -166,18 +177,26 @@ abstract class CtxHandler {
// Ok, we have closing position
Option(cp.AT()).fold({
// this is '=', e.g. total price
val total_price = handleAmount(cp.amount())
if ((total_price < 0 && 0 <= postAmount) || (postAmount < 0 && 0 <= total_price)) {
val total_cost = handleAmount(cp.amount())
if ((total_cost < 0 && 0 <= postAmount) || (postAmount < 0 && 0 <= total_cost)) {
val lineNro = postingCtx.start.getLine
val msg = "Error on line: " + lineNro.toString +
"; Value position (total price) has different sign than primary posting value"
"; Total cost '=' has different sign than primary posting value"
log.error(msg)
throw new CommodityException(msg)
}
(total_price, true)
(total_cost, true)
})(_ => {
// this is '@', e.g. unit price
(postAmount * handleAmount(cp.amount()), false)
val unit_price = handleAmount(cp.amount())
if (unit_price < 0) {
val lineNro = postingCtx.start.getLine
val msg = "Error on line: " + lineNro.toString +
"; Unit price '@' is negative"
log.error(msg)
throw new CommodityException(msg)
}
(postAmount * unit_price, false)
})
})
}
......
......@@ -168,6 +168,10 @@ class TacklerParserCommoditiesTest extends FunSpec {
| e 1 USD {1.20 EUR}
| a
|
|2017-01-01
| e -1 USD {1.20 EUR}
| a
|
|2019-01-01
| e 1 USD {1 €}
| a
......@@ -199,7 +203,7 @@ class TacklerParserCommoditiesTest extends FunSpec {
|""".stripMargin
val txns = tt.string2Txns(txnStr)
assert(txns.txns.size === 8)
assert(txns.txns.size === 9)
}
/**
......@@ -232,6 +236,10 @@ class TacklerParserCommoditiesTest extends FunSpec {
| e 1 USD {1.20 EUR} @ 1.09 EUR
| a
|
|2017-01-01
| e -1 USD {1.20 EUR} @ 1.09 EUR
| a
|
|2019-01-01
| e 1 USD {1 €} @ 1.09 €
| a
......@@ -263,7 +271,7 @@ class TacklerParserCommoditiesTest extends FunSpec {
|""".stripMargin
val txns = tt.string2Txns(txnStr)
assert(txns.txns.size === 8)
assert(txns.txns.size === 9)
}
/**
......@@ -288,113 +296,157 @@ class TacklerParserCommoditiesTest extends FunSpec {
}
describe("Invalid inputs and errors") {
/**
* test: 20b89e3e-a987-4e83-bd89-2cbf288caecc
*/
it("discrepancy of commodities '='") {
val txnStr =
"""
|2019-01-01
| e 1 € = 1 $
| a 1 € = 1 £
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
describe("Logical errors") {
/**
* test: 5af5d0d8-ca6e-4a03-a939-99d9d2a4ec43
*/
it("Unit cost '{ ... }' with negative value") {
val txnStr =
"""
|2017-01-01
| e 1.12 USD {-1.00 EUR}
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.contains("Unit cost"))
assert(ex.getMessage.contains("is negative"))
}
assert(ex.getMessage.startsWith("Different commodities without"))
}
/**
* test: 6f45f594-c4e6-449a-b6d2-7f25e9479bd5
*/
it("Value position (total price) with different sign (-1st vs. +2nd)") {
val txnStr =
"""
|2019-01-01
| e -1 $ = 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
/**
* test: a27b166c-e9c9-432c-bb9d-91915b51d76b
*/
it("Unit price '@' with negative value") {
val txnStr =
"""
|2019-01-01
| e 1 € @ -1.2 $
| a 1.2 $
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.contains("Unit price"))
assert(ex.getMessage.contains("is negative"))
}
assert(ex.getMessage.contains("different sign"))
}
/**
* test: aaf50217-1d04-49bd-a873-43a53be1c99f
*/
it("Value position (total price) with different sign (+1st vs. -2nd)") {
val txnStr =
"""
|2019-01-01
| e 1 $ = -1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
/**
* test: 6d1868da-3b9f-45e4-a2c6-db003da4c720
*/
it("Unit price '@' with same primary and secondary commodity") {
val txnStr =
"""
|2019-01-01
| e 1 € @ 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.startsWith("Error on line: 3; Both commodities are same for value position [€]"))
}
assert(ex.getMessage.contains("different sign"))
}
/**
* test: fe246259-2280-4d42-8360-6dd3e280b30a
*/
it("Unit price '@' with discrepancy of commodities") {
val txnStr =
"""
|2019-01-01
| e 1 € @ 1 $
| a 1 € @ 1 £
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.startsWith("Different commodities without"))
}
/**
* test: fe246259-2280-4d42-8360-6dd3e280b30a
*/
it("discrepancy of commodities '@'") {
val txnStr =
"""
|2019-01-01
| e 1 € @ 1 $
| a 1 € @ 1 £
|
|""".stripMargin
/**
* test: 6f45f594-c4e6-449a-b6d2-7f25e9479bd5
*/
it("Total cost '=' with different sign (-1st vs. +2nd)") {
val txnStr =
"""
|2019-01-01
| e -1 $ = 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.contains("Total cost"))
assert(ex.getMessage.contains("different sign"))
}
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
/**
* test: aaf50217-1d04-49bd-a873-43a53be1c99f
*/
it("Total cost '=' with different sign (+1st vs. -2nd)") {
val txnStr =
"""
|2019-01-01
| e 1 $ = -1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.contains("Total cost"))
assert(ex.getMessage.contains("different sign"))
}
assert(ex.getMessage.startsWith("Different commodities without"))
}
/**
* test: 6d1868da-3b9f-45e4-a2c6-db003da4c720
*/
it("value pos: same primary and secondary commodity ('@')") {
val txnStr =
"""
|2019-01-01
| e 1 € @ 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
/**
* test: aa52ac0a-278a-49e4-abad-fc2f00416a41
*/
it("Total cost '=' with same primary and secondary commodity") {
val txnStr =
"""
|2019-01-01
| e 1 € = 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.startsWith("Error on line: 3; Both commodities are same for value position [€]"))
}
assert(ex.getMessage.startsWith("Error on line: 3; Both commodities are same for value position [€]"))
}
/**
* test: aa52ac0a-278a-49e4-abad-fc2f00416a41
*/
it ("value pos: same primary and secondary commodity ('=')") {
val txnStr =
"""
|2019-01-01
| e 1 € = 1 €
| a
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
/**
* test: 20b89e3e-a987-4e83-bd89-2cbf288caecc
*/
it("Total cost '=' with discrepancy of commodities") {
val txnStr =
"""
|2019-01-01
| e 1 € = 1 $
| a 1 € = 1 £
|
|""".stripMargin
val ex = intercept[CommodityException] {
tt.string2Txns(txnStr)
}
assert(ex.getMessage.startsWith("Different commodities without"))
}
assert(ex.getMessage.startsWith("Error on line: 3; Both commodities are same for value position [€]"))
}
/**
......
......@@ -175,29 +175,37 @@ features:
tests:
errors:
- error:
id: 20b89e3e-a987-4e83-bd89-2cbf288caecc
id: 5af5d0d8-ca6e-4a03-a939-99d9d2a4ec43
name: TacklerParserCommoditiesTest
desc: "discrepancy of commodities '='"
desc: "Unit cost '{ ... }' with negative value"
- error:
id: fe246259-2280-4d42-8360-6dd3e280b30a
id: a27b166c-e9c9-432c-bb9d-91915b51d76b
name: TacklerParserCommoditiesTest
desc: "discrepancy of commodities '@'"
desc: "Unit price '@' with negative value"
- error:
id: 6d1868da-3b9f-45e4-a2c6-db003da4c720
name: TacklerParserCommoditiesTest
desc: "value pos: same primary and secondary commodity '@'"
desc: "Unit price '@' with same primary and secondary commodity"
- error:
id: aa52ac0a-278a-49e4-abad-fc2f00416a41
id: fe246259-2280-4d42-8360-6dd3e280b30a
name: TacklerParserCommoditiesTest
desc: "value pos: same primary and secondary commodity '='"
desc: "Unit price '@' with discrepancy of commodities"
- error:
id: 6f45f594-c4e6-449a-b6d2-7f25e9479bd5
name: TacklerParserCommoditiesTest
desc: "Value position (total price) with different sign (-1st vs. +2nd)"
desc: "Total cost '=' with different sign (-1st vs. +2nd)"
- error:
id: aaf50217-1d04-49bd-a873-43a53be1c99f
name: TacklerParserCommoditiesTest
desc: "Value position (total price) with different sign (+1st vs. -2nd)"
desc: "Total cost '=' with different sign (+1st vs. -2nd)"
- error:
id: aa52ac0a-278a-49e4-abad-fc2f00416a41
name: TacklerParserCommoditiesTest
desc: "Total cost '=' with same primary and secondary commodity"
- error:
id: 20b89e3e-a987-4e83-bd89-2cbf288caecc
name: TacklerParserCommoditiesTest
desc: "Total cost '=' with discrepancy of commodities"
- error:
id: 4babf379-9d88-49f3-8158-b9b7ff4e6eed
name: TacklerParserCommoditiesTest
......@@ -303,15 +311,15 @@ features:
- error:
id: bed02ea9-4191-4c98-b847-6b4e2a0fcb2d
name: TacklerParserCommoditiesTest
desc: "parse-only: missing commodity with opening position"
desc: "perr: with opening (comm)"
- error:
id: ac4a6183-fb21-4847-8b3e-912f21fe5a6b
name: TacklerParserCommoditiesTest
desc: "parse-only: missing value with opening position"
desc: "perr: with opening (value)"
- error:
id: 436d9ed5-b7a0-4e37-a7b4-86b00eb60e83
name: TacklerParserCommoditiesTest
desc: "parse-only: missing @"
desc: "perr: with missing @"
operations:
- test:
id: 9f711991-c9ae-4558-923c-95a69faff8bc
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment