-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexpected.json
More file actions
320 lines (320 loc) · 10.3 KB
/
Copy pathexpected.json
File metadata and controls
320 lines (320 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
{
"fixture": "eval/fixture-java",
"notes": "Deliberately-buggy Java (Spring Boot + JPA + H2) billing fixture, full coverage across all 10 domains (STO, ROU, IDE, LED, FX, TIM, AGG, TAX, API, TST). Green by design: mvn test passes (31 tests) because every fixture input is round-number and single-currency USD and every test exercises a happy path (one webhook delivery, one sequential balance update, no fractional cents), so the planted bugs stay dormant. Mirrors eval/fixture-py and eval/fixture. Bug-naming comments were stripped so an auditor finds the bugs blind. Files are basenames; the scorer matches a finding when rule id is equal AND finding.file ends with one of these (so a deep path like .../billing/Money.java matches Money.java).",
"planted": [
{
"key": "P01",
"rule": "API-3",
"files": [
"Money.java"
],
"note": "parseAmount coerces a money string with Double.parseDouble instead of BigDecimal, inexact binary on money input"
},
{
"key": "P02",
"rule": "STO-1",
"files": [
"Money.java"
],
"note": "money flows through double everywhere (parseAmount returns double, roundMoney/fromMinorUnits operate on double) instead of BigDecimal or integer minor units"
},
{
"key": "P03",
"rule": "STO-5",
"files": [
"Money.java"
],
"note": "toMinorUnits and fromMinorUnits hardcode *100 and /100 for every currency, no ISO 4217 exponent lookup (breaks JPY and BHD)"
},
{
"key": "P04",
"rule": "ROU-1",
"files": [
"Money.java"
],
"note": "roundMoney is the shared money rounding helper and uses Math.round(value*100)/100.0 on a double, no explicit RoundingMode"
},
{
"key": "P05",
"rule": "STO-7",
"files": [
"Money.java"
],
"note": "toDecimal builds new BigDecimal(amount) from a double argument, inheriting the inexact binary tail instead of new BigDecimal(String)"
},
{
"key": "P06",
"rule": "ROU-3",
"files": [
"Invoice.java"
],
"note": "invoiceTotal rounds each intermediate step then compounds: round the discounted subtotal, then round the taxed amount, so order and intermediate rounding are baked in"
},
{
"key": "P07",
"rule": "ROU-4",
"files": [
"Invoice.java"
],
"note": "bundleLineTotal divides before multiplying: a per-unit price rounded from bundleTotal/quantity is then multiplied back by quantity"
},
{
"key": "P08",
"rule": "ROU-2",
"files": [
"Split.java"
],
"note": "splitProportionally independently rounds each share with no residual reconciliation, so parts do not sum back to total on non-clean ratios"
},
{
"key": "P09",
"rule": "TAX-1",
"files": [
"Tax.java"
],
"note": "invoiceTaxByLines rounds tax per line while invoiceTaxOnTotal rounds on the invoice total, with no reconciliation between the two paths"
},
{
"key": "P10",
"rule": "TAX-2",
"files": [
"Tax.java"
],
"note": "extractTaxFromGross computes embedded tax as gross*rate instead of gross - gross/(1+rate), an inclusive versus exclusive mixup"
},
{
"key": "P11",
"rule": "TAX-3",
"files": [
"Tax.java"
],
"note": "SALES_TAX_RATE = 0.0825 stored as a bare double and multiplied on doubles, no BigDecimal rate"
},
{
"key": "P12",
"rule": "FX-1",
"files": [
"Fx.java"
],
"note": "refundInOriginalCurrency assumes a lossless round-trip, converting back via independently rounded rates (0.92 vs 1.087, not 1/rate)"
},
{
"key": "P13",
"rule": "FX-2",
"files": [
"Fx.java"
],
"note": "RATES is a mutable static map used as the rate source with no value, source, or timestamp captured on any converted amount"
},
{
"key": "P14",
"rule": "FX-3",
"files": [
"Fx.java"
],
"note": "settleInvoice overwrites the invoice total and currency in place, dropping the original billed amount and currency"
},
{
"key": "P15",
"rule": "FX-4",
"files": [
"Fx.java"
],
"note": "totalRevenue adds invoice totals across currencies with no currency guard, summing USD, EUR, and MXN as one unit"
},
{
"key": "P16",
"rule": "TIM-2",
"files": [
"Interest.java"
],
"note": "daysBetween computes (end.getEpochSecond() - start.getEpochSecond())/86400, naive of DST so any non-86400s day is miscounted, instead of ChronoUnit.DAYS.between"
},
{
"key": "P17",
"rule": "TIM-4",
"files": [
"Interest.java"
],
"note": "accruedInterest uses a hardcoded annualRate/365 daily rate with no day-count convention"
},
{
"key": "P18",
"rule": "TIM-3",
"files": [
"Interest.java"
],
"note": "transactionsInPeriod treats both endpoints as inclusive (>= start and <= end), so a transaction on a shared boundary is double-counted across consecutive periods"
},
{
"key": "P19",
"rule": "AGG-1",
"files": [
"Reports.java"
],
"note": "sumAmounts totals money by adding raw doubles in a loop, accumulating binary-float error instead of BigDecimal or minor units"
},
{
"key": "P20",
"rule": "AGG-3",
"files": [
"Reports.java"
],
"note": "totalAllPages paginates with limit and offset over live data, so inserts or deletes between fetches make the aggregate drift"
},
{
"key": "P21",
"rule": "STO-1",
"files": [
"schema.sql",
"LedgerEntry.java",
"WebhookEvent.java"
],
"note": "money columns typed DOUBLE PRECISION and REAL (subtotal, tax, total, unit_price, payment amount, webhook amount, ledger amount); the JPA entities map them to double fields"
},
{
"key": "P22",
"rule": "STO-2",
"files": [
"schema.sql",
"Account.java"
],
"note": "accounts.balance is NUMERIC(6,2) / @Column(precision=6, scale=2), caps at 9999.99 so a real running balance overflows"
},
{
"key": "P23",
"rule": "STO-4",
"files": [
"schema.sql",
"Account.java",
"LedgerEntry.java",
"WebhookEvent.java"
],
"note": "no currency column or field anywhere amounts are stored (accounts, invoices, payments, webhook_events, ledger_entries)"
},
{
"key": "P24",
"rule": "IDE-4",
"files": [
"schema.sql",
"WebhookEvent.java"
],
"note": "payments.external_ref and webhook_events.event_id have no UNIQUE constraint (no @Column(unique=true)), so duplicate provider refs and event ids insert fine"
},
{
"key": "P25",
"rule": "TIM-1",
"files": [
"schema.sql",
"LedgerEntry.java",
"WebhookEvent.java"
],
"note": "financial instants typed TIMESTAMP without time zone / mapped to LocalDateTime (issued_at, received_at, created_at)"
},
{
"key": "P26",
"rule": "IDE-1",
"files": [
"WebhookService.java",
"WebhookController.java"
],
"note": "handlePaymentSucceeded credits with no event-id dedup before the side effect, and the @PostMapping(/webhooks) controller has no guard either, so a redelivered webhook double-credits"
},
{
"key": "P27",
"rule": "IDE-2",
"files": [
"WebhookService.java"
],
"note": "chargeCustomer retries the POST to /charges with no Idempotency-Key header, a timed-out-but-successful first attempt is charged again on retry"
},
{
"key": "P28",
"rule": "IDE-3",
"files": [
"Store.java"
],
"note": "creditAccount and debitAccount do a findById read-modify-write on the balance with no @Version, no row lock, and no atomic UPDATE"
},
{
"key": "P29",
"rule": "LED-1",
"files": [
"Ledger.java"
],
"note": "correctEntry mutates the posted entry amount in place (setAmount) instead of appending a reversing entry"
},
{
"key": "P30",
"rule": "LED-2",
"files": [
"Ledger.java"
],
"note": "postEntry records a single signed leg, no paired debit and credit, no shared transaction id, so debits and credits are never forced to balance"
},
{
"key": "P31",
"rule": "LED-3",
"files": [
"Ledger.java"
],
"note": "the account balance is a cached mutable column (Account.balance) incremented separately, never derived from summing the entries"
},
{
"key": "P32",
"rule": "LED-4",
"files": [
"Ledger.java",
"LedgerEntry.java"
],
"note": "entries and corrections carry no actor or reason, so a posted movement cannot say who moved the money or why"
},
{
"key": "P33",
"rule": "API-1",
"files": [
"Api.java"
],
"note": "invoiceResponse serializes total as a bare JSON number (double via fromMinorUnits) and drops the currency from the payload"
},
{
"key": "P34",
"rule": "API-4",
"files": [
"Api.java"
],
"note": "invoiceResponse returns decimal dollars while accountBalanceResponse returns integer cents, two endpoints disagree on the money unit with no canonical shape"
},
{
"key": "P35",
"rule": "TST-1",
"files": [
"StoreTest.java",
"LedgerTest.java",
"WebhookServiceTest.java",
"SplitTest.java"
],
"note": "no property-based tests, no jqwik @Property, so invariants like credited-once and sum-of-parts-equals-total are never generated against"
},
{
"key": "P36",
"rule": "TST-2",
"files": [
"MoneyTest.java",
"TaxTest.java",
"InvoiceTest.java"
],
"note": "every fixture amount is round (100, 50, 60, 40, 250.00, 10000), no fractional-cent or .xx5 tie case, so no rounding mode is ever distinguished"
},
{
"key": "P37",
"rule": "TST-3",
"files": [
"MoneyTest.java",
"FxTest.java",
"TaxTest.java"
],
"note": "single happy currency USD, no JPY or BHD zero- or three-decimal case, no negative amount, no refund or reversal"
}
]
}