• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TampereHacklab / mulysa / 14019590506

23 Mar 2025 01:43PM UTC coverage: 86.506% (-0.2%) from 86.728%
14019590506

push

github

web-flow
Revert Osuuspankki workaround (#572)

* revert OP workaround and start using valueDate for transactiondates

* blackened

* unique

* makemessages

* differences on how makemessages work between libraries

2 of 2 new or added lines in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

2340 of 2705 relevant lines covered (86.51%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.63
/utils/test_dataimport.py
1
import io
1✔
2
import json
1✔
3
from datetime import date, timedelta
1✔
4
from decimal import Decimal
1✔
5
import unittest
1✔
6

7
from django.core.files.uploadedfile import SimpleUploadedFile
1✔
8

9
from django.contrib.auth import get_user_model
1✔
10
from django.test import TestCase
1✔
11
from django.utils import timezone
1✔
12

13
from .dataimport import DataImport
1✔
14
from users import models
1✔
15

16

17
class TestServiceSubscriptionContinuationWithImport(TestCase):
1✔
18
    def setUp(self):
1✔
19
        # one active user with subscription that is about to go overdue
20
        self.user = get_user_model().objects.create_customuser(
1✔
21
            first_name="FirstName",
22
            last_name="LastName",
23
            email="user1@example.com",
24
            birthday=timezone.now(),
25
            municipality="City",
26
            nick="user1",
27
            phone="+358123123",
28
        )
29
        self.memberservice = models.MemberService.objects.create(
1✔
30
            name="TestService",
31
            cost=10,
32
            days_per_payment=30,
33
        )
34
        self.servicesubscription = models.ServiceSubscription.objects.create(
1✔
35
            user=self.user,
36
            service=self.memberservice,
37
            state=models.ServiceSubscription.OVERDUE,
38
            # payment is over two "days_per_payment"
39
            paid_until=timezone.now().date() + timedelta(days=-50),
40
            reference_number="11112",
41
        )
42

43
    def _createTitodata(self, date, amount):
1✔
44
        datestr = date.strftime("%y%m%d")
1✔
45
        amount = amount * 100
1✔
46
        return {
1✔
47
            "aineistotunnus": "T",
48
            "tietuetunnus": "10",
49
            "tietueenpituus": "188",
50
            "tapahtumannumero": "1".rjust(6, "0"),
51
            "arkistointitunnus": datestr.rjust(18, "0"),
52
            "kirjauspaiva": datestr,
53
            "arvopaiva": datestr,
54
            "maksupaiva": datestr,
55
            "tapahtumatunnus": "1",  # 1 pano, 2 otto, 3 panon korjaus, 4 oton korjaus
56
            "kirjausselitekoodi": "719",
57
            "kirjausselite": "Viitemaksu".ljust(35),
58
            "rahamaaraetumerkki": "+",
59
            "rahamaara": str(amount).rjust(18, "0"),  # two decimals
60
            "kuittikoodi": " ",  # assumed to be empty
61
            "valitystapa": " ",  # assumed to be empty
62
            "saaja_maksaja": "TESTI HENKIL[".ljust(35),
63
            "nimen_lahde": "J",  # no idea what this means
64
            "saajan_tili": "".ljust(14),  # seems to be empty when money comes in
65
            "saajan_tili_muuttunut": " ",  # yup, no idea
66
            "viite": "11112".rjust(20, "0"),
67
            "lomakkeen_numero": "".ljust(8),  # yup, no idea again
68
            "tasotunnus": " ",  # empty means that it is really in the account :)
69
        }
70

71
    def test_user_gets_more_time(self):
1✔
72
        paid_delta = -50
1✔
73
        # starts of as overdue
74
        self.assertEqual(
1✔
75
            self.servicesubscription.state, models.ServiceSubscription.OVERDUE
76
        )
77
        self.assertEqual(
1✔
78
            self.servicesubscription.paid_until,
79
            timezone.now().date() + timedelta(days=paid_delta),
80
        )
81

82
        # makes one payment two days ago, still overdue but the date has changed
83
        data = self._createTitodata(timezone.now().date() + timedelta(days=-2), 10)
1✔
84
        dataline = "".join(data.values())
1✔
85
        lines = io.BytesIO(b"header\n" + dataline.encode())
1✔
86
        results = DataImport.import_tito(lines)
1✔
87
        self.assertDictEqual(
1✔
88
            results, {"imported": 1, "exists": 0, "error": 0, "failedrows": []}
89
        )
90
        paid_delta += 30
1✔
91

92
        self.servicesubscription.refresh_from_db()
1✔
93
        self.assertEqual(
1✔
94
            self.servicesubscription.state, models.ServiceSubscription.OVERDUE
95
        )
96
        self.assertEqual(
1✔
97
            self.servicesubscription.paid_until,
98
            timezone.now().date() + timedelta(days=paid_delta),
99
        )
100

101
        # makes another payment yesterday, state PAID and date in future
102
        data = self._createTitodata(timezone.now().date() + timedelta(days=-1), 10)
1✔
103
        dataline = "".join(data.values())
1✔
104
        lines = io.BytesIO(b"header\n" + dataline.encode())
1✔
105
        results = DataImport.import_tito(lines)
1✔
106
        self.assertDictEqual(
1✔
107
            results, {"imported": 1, "exists": 0, "error": 0, "failedrows": []}
108
        )
109
        paid_delta += 30
1✔
110

111
        self.servicesubscription.refresh_from_db()
1✔
112
        self.assertEqual(
1✔
113
            self.servicesubscription.state, models.ServiceSubscription.ACTIVE
114
        )
115
        self.assertEqual(
1✔
116
            self.servicesubscription.paid_until,
117
            timezone.now().date() + timedelta(days=paid_delta),
118
        )
119

120
    def tearDown(self):
1✔
121
        self.user.delete()
1✔
122
        self.memberservice.delete()
1✔
123
        self.servicesubscription.delete()
1✔
124
        models.BankTransaction.objects.all().delete()
1✔
125

126

127
class TestTitoImporter(TestCase):
1✔
128
    def _getbasetitodata(self):
1✔
129
        """
130
        TODO: this should really be its own class that knows how to
131
        generate the real line data. for now this will have to do
132
        if you change the values remember to keep the length correct
133
        """
134
        return {
1✔
135
            "aineistotunnus": "T",
136
            "tietuetunnus": "10",
137
            "tietueenpituus": "188",
138
            "tapahtumannumero": "1".rjust(6, "0"),
139
            "arkistointitunnus": "ABC".rjust(18, "0"),
140
            "kirjauspaiva": "200916",
141
            "arvopaiva": "200916",
142
            "maksupaiva": "200916",
143
            "tapahtumatunnus": "1",  # 1 pano, 2 otto, 3 panon korjaus, 4 oton korjaus
144
            "kirjausselitekoodi": "719",
145
            "kirjausselite": "Viitemaksu".ljust(35),
146
            "rahamaaraetumerkki": "+",
147
            "rahamaara": "12300".rjust(18, "0"),  # two decimals
148
            "kuittikoodi": " ",  # assumed to be empty
149
            "valitystapa": " ",  # assumed to be empty
150
            "saaja_maksaja": "TESTI HENKIL[".ljust(35),
151
            "nimen_lahde": "J",  # no idea what this means
152
            "saajan_tili": "".ljust(14),  # seems to be empty when money comes in
153
            "saajan_tili_muuttunut": " ",  # yup, no idea
154
            "viite": "11112".rjust(20, "0"),
155
            "lomakkeen_numero": "".ljust(8),  # yup, no idea again
156
            "tasotunnus": " ",  # empty means that it is really in the account :)
157
        }
158

159
    def test_tito_import_generated_data(self):
1✔
160
        """
161
        Test that the generated data above works
162
        """
163
        models.BankTransaction.objects.all().delete()
1✔
164
        data = self._getbasetitodata()
1✔
165
        dataline = "".join(data.values())
1✔
166
        lines = io.BytesIO(b"header\n" + dataline.encode())
1✔
167
        results = DataImport.import_tito(lines)
1✔
168
        self.assertDictEqual(
1✔
169
            results, {"imported": 1, "exists": 0, "error": 0, "failedrows": []}
170
        )
171
        self.assertEqual(
1✔
172
            models.BankTransaction.objects.first().reference_number,
173
            data["viite"].strip().strip("0"),
174
        )
175

176
    def test_tito_import_wrong_length(self):
1✔
177
        models.BankTransaction.objects.all().delete()
1✔
178
        data = self._getbasetitodata()
1✔
179
        # remove one character from the end
180
        dataline = "".join(data.values())[:-1]
1✔
181
        lines = io.BytesIO(b"header\n" + dataline.encode())
1✔
182
        results = DataImport.import_tito(lines)
1✔
183
        self.assertDictContainsSubset({"imported": 0, "exists": 0, "error": 1}, results)
1✔
184

185
    def test_tito_import_empty_file(self):
1✔
186
        """
187
        empty file
188
        """
189
        data = io.BytesIO(b"")
1✔
190
        results = DataImport.import_tito(data)
1✔
191
        self.assertDictEqual(
1✔
192
            results, {"imported": 0, "exists": 0, "error": 0, "failedrows": []}
193
        )
194

195
    def test_tito_import_invalid_file(self):
1✔
196
        """
197
        random data
198
        """
199
        data = io.BytesIO(b"foo\nbar")
1✔
200
        results = DataImport.import_tito(data)
1✔
201
        self.assertDictEqual(
1✔
202
            results,
203
            {
204
                "imported": 0,
205
                "exists": 0,
206
                "error": 1,
207
                "failedrows": ["bar (Empty line or not starting with T)"],
208
            },
209
        )
210

211
    def test_tito_import_production_data(self):
1✔
212
        """
213
        this data is almost direct from real world data, some values have just
214
        been changed so that the persons identiy is not compromised
215
        """
216
        models.BankTransaction.objects.all().delete()
1✔
217
        data = io.BytesIO(
1✔
218
            b"header\nT101880000011111111111111111112009252009252009251"
219
            b"710Viitemaksu                         +000000000000012300  "
220
            b"TESTI HENKIL[                      J               "
221
            b"00000000000000011112         "
222
        )
223
        results = DataImport.import_tito(data)
1✔
224
        self.assertDictEqual(
1✔
225
            results, {"imported": 1, "exists": 0, "error": 0, "failedrows": []}
226
        )
227

228
        # and same again to see that it found it again
229
        data = io.BytesIO(
1✔
230
            b"header\nT101880000011111111111111111112009252009252009251"
231
            b"710Viitemaksu                         +000000000000012300  "
232
            b"TESTI HENKIL[                      J               "
233
            b"00000000000000011112         "
234
        )
235
        results = DataImport.import_tito(data)
1✔
236
        self.assertDictEqual(
1✔
237
            results, {"imported": 0, "exists": 1, "error": 0, "failedrows": []}
238
        )
239

240
    def test_tito_cents(self):
1✔
241
        models.BankTransaction.objects.all().delete()
1✔
242
        data = self._getbasetitodata()
1✔
243
        # with 45 cents
244
        data["rahamaara"] = "12345".rjust(18, "0")
1✔
245
        dataline = "".join(data.values())
1✔
246
        lines = io.BytesIO(b"header\n" + dataline.encode())
1✔
247
        results = DataImport.import_tito(lines)
1✔
248
        self.assertDictEqual(
1✔
249
            results, {"imported": 1, "exists": 0, "error": 0, "failedrows": []}
250
        )
251
        self.assertEqual(
1✔
252
            models.BankTransaction.objects.first().reference_number,
253
            data["viite"].strip().strip("0"),
254
        )
255
        self.assertEqual(
1✔
256
            models.BankTransaction.objects.first().amount,
257
            Decimal("123.45"),
258
            "Check decimals",
259
        )
260

261

262
class TestHolviImporter(TestCase):
1✔
263
    def test_holvi_import_2022_05_format(self):
1✔
264
        """
265
        Test import with data format from 2022-05-18
266

267
        Uses same fields but XLSX file format.
268
        """
269
        models.BankTransaction.objects.all().delete()
1✔
270
        xls = open("utils/holvi-account-test-statement-2022-05.xlsx", "rb")
1✔
271
        name = xls.name
1✔
272
        data = xls.read()
1✔
273
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
274
        self.assertDictEqual(
1✔
275
            res, {"imported": 5, "exists": 0, "error": 0, "failedrows": []}
276
        )
277

278
        # and again to test that it found the same rows
279
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
280
        self.assertDictEqual(
1✔
281
            res, {"imported": 0, "exists": 5, "error": 0, "failedrows": []}
282
        )
283

284
        # quick check for the first imported item data, check the first line of
285
        # holvi-account-test-statement-2022-05.xlsx
286
        firstimported = models.BankTransaction.objects.first()
1✔
287
        self.assertEqual(firstimported.date, date(2022, 3, 30))
1✔
288
        self.assertEqual(firstimported.amount, Decimal("-135.9"))
1✔
289
        self.assertEqual(firstimported.reference_number, "1122002246684")
1✔
290
        self.assertEqual(
1✔
291
            firstimported.archival_reference, "285ef4ccf4957ea2ba807b961360bf26"
292
        )
293

294
    def test_holvi_import_2022_10_format(self):
1✔
295
        """
296
        Test import with data format from 2022-10-01
297

298
        Uses same fields but XLSX file format.
299
        """
300
        models.BankTransaction.objects.all().delete()
1✔
301
        xls = open("utils/holvi-account-test-statement-2022-10.xlsx", "rb")
1✔
302
        name = xls.name
1✔
303
        data = xls.read()
1✔
304
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
305
        self.assertDictEqual(
1✔
306
            res, {"imported": 11, "exists": 0, "error": 0, "failedrows": []}
307
        )
308

309
        # and again to test that it found the same rows
310
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
311
        self.assertDictEqual(
1✔
312
            res, {"imported": 0, "exists": 11, "error": 0, "failedrows": []}
313
        )
314

315
        # quick check for the first imported item data, check the first line of
316
        # holvi-account-test-statement-2022-10.xlsx
317
        firstimported = models.BankTransaction.objects.first()
1✔
318
        self.assertEqual(firstimported.date, date(2022, 3, 30))
1✔
319
        self.assertEqual(firstimported.amount, Decimal("-135.9"))
1✔
320
        self.assertEqual(firstimported.reference_number, "1122002246684")
1✔
321
        self.assertEqual(
1✔
322
            firstimported.archival_reference, "285ef4ccf4957ea2ba807b961360bf26"
323
        )
324

325
        # and the last as it has 'Sept' in the dateformat
326
        lastimported = models.BankTransaction.objects.last()
1✔
327
        self.assertEqual(lastimported.date, date(2022, 9, 4))
1✔
328
        self.assertEqual(lastimported.amount, Decimal("30"))
1✔
329
        # note, BankTransaction does not keep leading zeroes
330
        self.assertEqual(lastimported.reference_number, "200047")
1✔
331
        self.assertEqual(
1✔
332
            lastimported.archival_reference, "1924ceba5a3b1c5ffea892fb4850e00d"
333
        )
334

335
    def test_holvi_import_2025_01_format(self):
1✔
336
        """
337
        Test import with data format from 2025-01-01
338

339
        Changed field headers.
340
        """
341
        models.BankTransaction.objects.all().delete()
1✔
342
        xls = open("utils/holvi-account-test-statement-2025-01.xlsx", "rb")
1✔
343
        name = xls.name
1✔
344
        data = xls.read()
1✔
345
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
346
        self.assertDictEqual(
1✔
347
            res, {"imported": 11, "exists": 0, "error": 0, "failedrows": []}
348
        )
349

350
        # and again to test that it found the same rows
351
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
352
        self.assertDictEqual(
1✔
353
            res, {"imported": 0, "exists": 11, "error": 0, "failedrows": []}
354
        )
355

356
        # quick check for the first imported item data, check the first line of
357
        # holvi-account-test-statement-2025-01.xlsx
358
        firstimported = models.BankTransaction.objects.first()
1✔
359
        self.assertEqual(firstimported.date, date(2022, 3, 30))
1✔
360
        self.assertEqual(firstimported.amount, Decimal("-135.9"))
1✔
361
        self.assertEqual(firstimported.reference_number, "1122002246684")
1✔
362
        self.assertEqual(
1✔
363
            firstimported.archival_reference, "285ef4ccf4957ea2ba807b961360bf26"
364
        )
365

366
        # and the last as it has 'Sept' in the dateformat
367
        lastimported = models.BankTransaction.objects.last()
1✔
368
        self.assertEqual(lastimported.date, date(2022, 9, 4))
1✔
369
        self.assertEqual(lastimported.amount, Decimal("30"))
1✔
370
        # note, BankTransaction does not keep leading zeroes
371
        self.assertEqual(lastimported.reference_number, "200047")
1✔
372
        self.assertEqual(
1✔
373
            lastimported.archival_reference, "1924ceba5a3b1c5ffea892fb4850e00d"
374
        )
375

376
    def test_holvi_cents(self):
1✔
377
        models.BankTransaction.objects.all().delete()
1✔
378
        xls = open("utils/holvi-account-test-statement-2022-05.xlsx", "rb")
1✔
379
        name = xls.name
1✔
380
        data = xls.read()
1✔
381
        res = DataImport.import_holvi(SimpleUploadedFile(name, data))
1✔
382
        self.assertDictEqual(
1✔
383
            res, {"imported": 5, "exists": 0, "error": 0, "failedrows": []}
384
        )
385
        # third row on the test file
386
        transaction = models.BankTransaction.objects.get(
1✔
387
            archival_reference="0b914e1f528d902e6fe1ee7ff792ce5f"
388
        )
389
        self.assertEqual(transaction.amount, Decimal("-7.44"), "Check decimals")
1✔
390

391
    def tearDown(self):
1✔
392
        models.BankTransaction.objects.all().delete()
1✔
393

394

395
class TestNordigenmporter(TestCase):
1✔
396
    def test_nordigen(self):
1✔
397
        """
398
        Test importing nordigen transactions
399
        """
400
        models.BankTransaction.objects.all().delete()
1✔
401

402
        self.maxDiff = None
1✔
403

404
        with open("utils/nordigen_transactions.json") as json_file:
1✔
405
            data = json.load(json_file)
1✔
406
            res = DataImport.import_nordigen(data)
1✔
407

408
            self.assertDictEqual(
1✔
409
                res,
410
                {
411
                    "imported": 6,
412
                    "exists": 1,
413
                    "error": 1,
414
                    "failedrows": [
415
                        "{'transactionId': 'TEST4', 'entryReference': None, "
416
                        "'bookingDate': '2022-10-24', 'valueDate': '2022-10-23', "
417
                        "'transactionAmount': {'amount': '80.00', 'currency': 'SEK'}, "
418
                        "'debtorName': 'TESTER 4', 'debtorAccount': {'iban': "
419
                        "'FI11111111'}, 'remittanceInformationUnstructured': "
420
                        "'Viitemaksu invalid currency', 'additionalInformation': 'Viitemaksu'} (Cannot "
421
                        "handle different currencies)"
422
                    ],
423
                },
424
            )
425
            test5 = models.BankTransaction.objects.get(archival_reference="TEST5")
1✔
426
            self.assertEqual(test5.amount, Decimal("80.00"))
1✔
427
            self.assertEqual(test5.reference_number, "123")
1✔
428

429
    def tearDown(self):
1✔
430
        models.BankTransaction.objects.all().delete()
1✔
431

432

433
@unittest.skip(
1✔
434
    "Skipped until https://github.com/TampereHacklab/mulysa/issues/570 is completed"
435
)
436
class TestBankTransactionUnique(TestCase):
1✔
437
    """
438
    At least OP does a strange thing where recurring transactions get the same transactionId for each recurring payment.
439
    Previously this caused a problem where only the first transaction was imported and the rest were ignored.
440
    Now the uniqueness is with transactionid and the date.
441
    """
442

443
    def test_same_transactionid(self):
1✔
UNCOV
444
        with open("utils/nordigen_transactions_duplicates.json") as json_file:
×
UNCOV
445
            data = json.load(json_file)
×
UNCOV
446
            res = DataImport.import_nordigen(data)
×
447

UNCOV
448
            self.assertDictEqual(
×
449
                res,
450
                {
451
                    "imported": 2,
452
                    "exists": 1,
453
                    "error": 0,
454
                    "failedrows": [],
455
                },
456
            )
457

458
            # REF1 should be found twice even even tho it was in the file three times (one was on the same day)
UNCOV
459
            self.assertEqual(
×
460
                2,
461
                models.BankTransaction.objects.filter(
462
                    archival_reference="REF1"
463
                ).count(),
464
            )
465

466
    def tearDown(self):
1✔
UNCOV
467
        models.BankTransaction.objects.all().delete()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc