diff --git a/swift-server/app-tests/app-test.swift b/swift-server/app-tests/app-test.swift index e54b985e..e8edcd67 100644 --- a/swift-server/app-tests/app-test.swift +++ b/swift-server/app-tests/app-test.swift @@ -6,7 +6,12 @@ import XCTest @testable import App -class TestError: Error {} +class TestError: Error { + let message: String? + init(message: String? = nil) { + self.message = message + } +} func createUser( app: Application, username: String, email: String, password: String, diff --git a/swift-server/app-tests/importers/importer-caixa-enginyers.tests.swift b/swift-server/app-tests/importers/importer-caixa-enginyers.tests.swift index ee8a2703..ce022df5 100644 --- a/swift-server/app-tests/importers/importer-caixa-enginyers.tests.swift +++ b/swift-server/app-tests/importers/importer-caixa-enginyers.tests.swift @@ -5,48 +5,13 @@ import XCTest @testable import App -final class CaixaEnginyersImporterTests: XCTestCase { - var importerService: NewImportService! - var bankTransactionService: BankTransactionService! - var statusReportsService: StatusReportsService! - var caixaEnginyersImporter: CaixaEnginyersAccountImporter! - var group: UserGroup! - var app: Application? - - func getTestFile(file: String) -> String { - let pwd = URL(fileURLWithPath: #file).pathComponents - .prefix(while: { $0 != "importers" }).joined(separator: "/").dropFirst() - return "\(pwd)/\(file)" - } - - override func setUp() async throws { - let app = try await Application.make(.testing) - try await configure(app) - self.app = app - - self.group = UserGroup(name: "Test User Group") - try await self.group.save(on: app.db) - - caixaEnginyersImporter = CaixaEnginyersAccountImporter() - let testParsers: [ParserFactory] = [ - caixaEnginyersImporter, CaixaEnginiersCreditImporter(), - ] - importerService = NewImportService(parsers: testParsers) - bankTransactionService = BankTransactionService() - statusReportsService = StatusReportsService() - } - - override func tearDown() async throws { - try await self.app?.asyncShutdown() - self.app = nil - } - - func getDb() throws -> Database { - guard let app = app else { - throw TestError() - } - return app.db - } +final class CaixaEnginyersImporterTests: BaseImporterTests { + + override func getParsers() throws -> [any ParserFactory] { + return [ + CaixaEnginyersAccountImporter(), CaixaEnginiersCreditImporter(), + ] + } func testCaixaEnginyersAccountImport() async throws { let groupOwnerId = try self.group.requireID() diff --git a/swift-server/app-tests/importers/importer-commerz-bank.swift b/swift-server/app-tests/importers/importer-commerz-bank.swift new file mode 100644 index 00000000..e44e74a3 --- /dev/null +++ b/swift-server/app-tests/importers/importer-commerz-bank.swift @@ -0,0 +1,57 @@ +import XCTest +@testable import App + +final class CommerzBankEnImporterTests: BaseImporterTests { + override func getParsers() throws -> [any ParserFactory] { + return [CommerzBankEnImporter()] + } + func testInsertingDataInCommerzBank() async throws { + let groupOwnerId = try self.group.requireID() + let db = try getDb() + + let filePath = getTestFile(file: "test_files/commerz_bank.CSV") + + try await importerService.importFromFile(on: db, groupOwnerId: groupOwnerId, key: "commerz-bank/en", fileName: "CommerzBank", filePath: filePath) + + + let reports = try await statusReportsService.getAll( + on: db, groupIds: [groupOwnerId]) + XCTAssertEqual(reports.list.count, 1) + XCTAssertEqual(reports.list.first?.status, "OK") + XCTAssertEqual(reports.list.first?.description, "") + XCTAssertNil(reports.list.first?.context) + + let transactions = try await bankTransactionService.getAll( + on: db, groupIds: [groupOwnerId]) + XCTAssertEqual(transactions.list.count, 5) + + let queryTest = transactions.list.first(where: { $0.date == DateOnly("2019-05-02") }) + XCTAssertEqual(queryTest?.value, 256.01) + XCTAssertEqual(queryTest?.movementName, "Concept and more concepts") + + let queryTest2 = transactions.list.first(where: { $0.date == DateOnly("2020-01-09") }) + XCTAssertEqual(queryTest2?.value, -25) + XCTAssertEqual(queryTest2?.movementName, "Commerzbank 0321554") + + let queryTest3 = transactions.list.first(where: { $0.date == DateOnly("2020-01-07") }) + XCTAssertEqual(queryTest3?.movementName, "Backerei Sipl GmbH Fil.35 GIR 69036") + + let queryTest4 = transactions.list.first(where: { $0.date == DateOnly("2020-02-09") }) + XCTAssertEqual(queryTest4?.movementName, "ARAL Some address") + + let queryTest5 = transactions.list.first(where: { $0.date == DateOnly("2020-02-07") }) + XCTAssertEqual(queryTest5?.movementName, "BACKSTUBE WUENSCHE GMBH") + } +} + +final class CommerzBankUnitTests: XCTestCase { + func testSplitMessage() throws { + let importerService = CommerzBankEnImporter() + + let (msg, details, date) = try importerService.splitMessage("Kartenzahlung ARAL Some address 2020-02-09T21:13:19 KFN 1 VJ 2442 Kartenzahlung") + XCTAssertEqual(msg, "ARAL Some address") + + + } +} + diff --git a/swift-server/app-tests/importers/importer-helper.swift b/swift-server/app-tests/importers/importer-helper.swift index 2124857b..5d5471ea 100644 --- a/swift-server/app-tests/importers/importer-helper.swift +++ b/swift-server/app-tests/importers/importer-helper.swift @@ -1,4 +1,6 @@ -import Foundation +import XCTest +import XCTVapor +import Fluent @testable import App @@ -68,3 +70,49 @@ class TestDynamicImporter: ParserFactory { } } } + +class BaseImporterTests: XCTestCase { + var importerService: NewImportService! + var bankTransactionService: BankTransactionService! + var statusReportsService: StatusReportsService! + var group: UserGroup! + var app: Application? + + func getTestFile(file: String) -> String { + let pwd = URL(fileURLWithPath: #file).pathComponents + .prefix(while: { $0 != "app-tests" }).joined(separator: "/").dropFirst() + return "\(pwd)/\(file)" + } + + func getParsers() throws -> [ParserFactory] { + throw TestError(message: "A class must overwrite getParsers") + } + + override func setUp() async throws { + let app = try await Application.make(.testing) + try await configure(app) + self.app = app + + self.group = UserGroup(name: "Test User Group") + try await self.group.save(on: app.db) + + let testParsers: [ParserFactory] = try getParsers() + importerService = NewImportService(parsers: testParsers) + bankTransactionService = BankTransactionService() + statusReportsService = StatusReportsService() + } + + override func tearDown() async throws { + try await self.app?.asyncShutdown() + self.app = nil + } + + func getDb() throws -> Database { + guard let app = app else { + throw TestError() + } + return app.db + } + + +} diff --git a/swift-server/app-tests/importers/importer-n26.tests.swift b/swift-server/app-tests/importers/importer-n26.tests.swift index 7aed2e47..404d5462 100644 --- a/swift-server/app-tests/importers/importer-n26.tests.swift +++ b/swift-server/app-tests/importers/importer-n26.tests.swift @@ -5,44 +5,11 @@ import XCTest @testable import App -final class N26ImporterTests: XCTestCase { - var importerService: NewImportService! - var bankTransactionService: BankTransactionService! - var statusReportsService: StatusReportsService! - var group: UserGroup! - var app: Application? - - func getTestFile(file: String) -> String { - let pwd = URL(fileURLWithPath: #file).pathComponents - .prefix(while: { $0 != "importers" }).joined(separator: "/").dropFirst() - return "\(pwd)/\(file)" - } - - override func setUp() async throws { - let app = try await Application.make(.testing) - try await configure(app) - self.app = app - - self.group = UserGroup(name: "Test User Group") - try await self.group.save(on: app.db) - - let testParsers: [ParserFactory] = [N26Importer()] - importerService = NewImportService(parsers: testParsers) - bankTransactionService = BankTransactionService() - statusReportsService = StatusReportsService() - } - - override func tearDown() async throws { - try await self.app?.asyncShutdown() - self.app = nil - } - - func getDb() throws -> Database { - guard let app = app else { - throw TestError() - } - return app.db - } +final class N26ImporterTests: BaseImporterTests { + + override func getParsers() throws -> [any ParserFactory] { + return [N26Importer()] + } func testN26Import() async throws { let groupOwnerId = try self.group.requireID() diff --git a/swift-server/app-tests/importers/importer-service.tests.swift b/swift-server/app-tests/importers/importer-service.tests.swift index d8552f6f..a1e129b9 100644 --- a/swift-server/app-tests/importers/importer-service.tests.swift +++ b/swift-server/app-tests/importers/importer-service.tests.swift @@ -5,43 +5,13 @@ import XCTest @testable import App -final class ImporterServiceTests: XCTestCase { - var importerService: NewImportService! - var bankTransactionService: BankTransactionService! - var statusReportsService: StatusReportsService! - var group: UserGroup! - var app: Application? - - override func setUp() async throws { - // let testParsers: [ParserFactory] = [TestBasicImporter(), N26Importer(), CommerzBankEnImporter()] - - let app = try await Application.make(.testing) - try await configure(app) - self.app = app - - self.group = UserGroup(name: "Test User Group") - try await self.group.save(on: app.db) - - let testParsers: [ParserFactory] = [ - TestBasicImporter(), - TestBasicImporter(key: "test-invalid-data", data: [["a": "b"]]), - ] - importerService = NewImportService(parsers: testParsers) - bankTransactionService = BankTransactionService() - statusReportsService = StatusReportsService() - } - - override func tearDown() async throws { - try await self.app?.asyncShutdown() - self.app = nil - } - - func getDb() throws -> Database { - guard let app = app else { - throw TestError() - } - return app.db - } +final class ImporterServiceTests: BaseImporterTests { + override func getParsers() throws -> [any ParserFactory] { + return [ + TestBasicImporter(), + TestBasicImporter(key: "test-invalid-data", data: [["a": "b"]]), + ] + } func testImportEverythingFine() async throws { let groupOwnerId = try self.group.requireID() diff --git a/swift-server/app/importers/importers/commerz-bank.importer.swift b/swift-server/app/importers/importers/commerz-bank.importer.swift new file mode 100644 index 00000000..052b45e8 --- /dev/null +++ b/swift-server/app/importers/importers/commerz-bank.importer.swift @@ -0,0 +1,92 @@ +import Foundation +import Vapor +import CSV + +class CommerzBankEnImporter: ParserFactory { + let DATE_REGEX = try! NSRegularExpression(pattern: "(?\\d{4})-(?\\d{2})-(?\\d{2})T\\d{2}:\\d{2}:\\d{2}", options: []) + let transformHelper: TransformHelper<[String?]> + let key: String = "commerz-bank/en" + let fileRegex: String = "Umsaetze_KtoNr.*\\.CSV" + + init() { + let fieldsMap = FieldsMap( + movementName: 9, + date: 11, + dateValue: 1, + details: 10, value: 4 + ) + self.transformHelper = TransformHelper(fieldsMap, dateFormat: "dd.MM.yyyy") + } + + func splitMessage(_ msg: String) throws -> (String, String?, String?){ + var message = msg + + if message.hasPrefix("Auszahlung") { + message.removeFirst("Auszahlung".count) + } else if message.hasPrefix("Kartenzahlung") { + message.removeFirst("Kartenzahlung".count) + } + + let dateMatch = DATE_REGEX.firstMatch(in: message, options: [], range: NSRange(location: 0, length: message.count)) + if let dateMatch = dateMatch { + let range = Range(dateMatch.range(at: 0), in: message) + guard let range = range else { + throw Exception(.E10000) + } + var movementName = String(message[.. AsyncThrowingStream { + AsyncThrowingStream { continuation in + Task { + do { + let csv = try parseCsv(filePath: filePath) + + var lineCounter = 0 + while let row = csv.next() { + lineCounter += 1 + do { + var mappedData : [String?] = row + + let (movementName, details, newDate) = try splitMessage(mappedData[3] ?? "") + + mappedData.append(movementName) + mappedData.append(details) + if let newDate = newDate { + mappedData.append(newDate) + } else { + mappedData.append(row[0]) + } + let transaction = try self.transformHelper.map(mappedData) + continuation.yield(transaction) + } catch { + throw Exception(.E10011, context: ["line": lineCounter], cause: error) + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + } + } +} + diff --git a/swift-server/app-tests/test_files/MovimientosCuenta.xls b/swift-server/test_files/MovimientosCuenta.xls similarity index 100% rename from swift-server/app-tests/test_files/MovimientosCuenta.xls rename to swift-server/test_files/MovimientosCuenta.xls diff --git a/swift-server/app-tests/test_files/MovimientosTarjetaCredito.xls b/swift-server/test_files/MovimientosTarjetaCredito.xls similarity index 100% rename from swift-server/app-tests/test_files/MovimientosTarjetaCredito.xls rename to swift-server/test_files/MovimientosTarjetaCredito.xls diff --git a/swift-server/app-tests/test_files/caixabank-account2020.xls b/swift-server/test_files/caixabank-account2020.xls similarity index 100% rename from swift-server/app-tests/test_files/caixabank-account2020.xls rename to swift-server/test_files/caixabank-account2020.xls diff --git a/swift-server/app-tests/test_files/caixabank-card-old.xls b/swift-server/test_files/caixabank-card-old.xls similarity index 100% rename from swift-server/app-tests/test_files/caixabank-card-old.xls rename to swift-server/test_files/caixabank-card-old.xls diff --git a/swift-server/app-tests/test_files/caixabank-card.xls b/swift-server/test_files/caixabank-card.xls similarity index 100% rename from swift-server/app-tests/test_files/caixabank-card.xls rename to swift-server/test_files/caixabank-card.xls diff --git a/swift-server/app-tests/test_files/caixabank-card2020.xls b/swift-server/test_files/caixabank-card2020.xls similarity index 100% rename from swift-server/app-tests/test_files/caixabank-card2020.xls rename to swift-server/test_files/caixabank-card2020.xls diff --git a/swift-server/app-tests/test_files/commerz_bank.CSV b/swift-server/test_files/commerz_bank.CSV similarity index 100% rename from swift-server/app-tests/test_files/commerz_bank.CSV rename to swift-server/test_files/commerz_bank.CSV diff --git a/swift-server/app-tests/test_files/commerz_bank_one.CSV b/swift-server/test_files/commerz_bank_one.CSV similarity index 100% rename from swift-server/app-tests/test_files/commerz_bank_one.CSV rename to swift-server/test_files/commerz_bank_one.CSV diff --git a/swift-server/app-tests/test_files/n26_es.csv b/swift-server/test_files/n26_es.csv similarity index 100% rename from swift-server/app-tests/test_files/n26_es.csv rename to swift-server/test_files/n26_es.csv diff --git a/swift-server/app-tests/test_files/transactions.qif b/swift-server/test_files/transactions.qif similarity index 100% rename from swift-server/app-tests/test_files/transactions.qif rename to swift-server/test_files/transactions.qif diff --git a/todo.md b/todo.md index c05bc3a6..3d7ba5d4 100644 --- a/todo.md +++ b/todo.md @@ -39,12 +39,14 @@ * [ ] Fix ui * [ ] Imports * [x] Import caixa enginyers - * [ ] Import n26 - * [ ] Import commerzbank + * [x] Import n26 + * [x] Import commerzbank * [ ] Import qif + * [ ] Crud api * [ ] Call to apply rules * [ ] Bank transactions * [x] get bank transactions data + * [] add/remove label * [ ] labels * [x] get labels * [ ] create label