Add FinTS mock test server for integration testing#40
Conversation
Agent-Logs-Url: https://github.com/larsdecker/fints/sessions/0df275c1-b8bf-4e68-91f8-0c403ad5eede Co-authored-by: larsdecker <1968186+larsdecker@users.noreply.github.com>
…ests Agent-Logs-Url: https://github.com/larsdecker/fints/sessions/0df275c1-b8bf-4e68-91f8-0c403ad5eede Co-authored-by: larsdecker <1968186+larsdecker@users.noreply.github.com>
Agent-Logs-Url: https://github.com/larsdecker/fints/sessions/0df275c1-b8bf-4e68-91f8-0c403ad5eede Co-authored-by: larsdecker <1968186+larsdecker@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33e8705160
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| profileVersion, msgNo, "9210", "Konto nicht gefunden"); | ||
| } | ||
|
|
||
| const balance = this.config.balances.find((b) => b.accountNumber === accountNumber); |
There was a problem hiding this comment.
Use matched account number for statement balance lookup
When an HKKAZ v7 request is matched via IBAN fallback, account is resolved correctly but balance is still looked up with the original accountNumber token from the request. In IBAN-only requests that token is the IBAN, so the lookup misses and openingBalance falls back to 0, which yields incorrect MT940 balances for a valid account. Use the resolved account.accountNumber for the balance query.
Useful? React with 👍 / 👎.
| req.on("end", () => { | ||
| try { | ||
| // Decode the Base64 request | ||
| const requestStr = decodeBase64(body); |
There was a problem hiding this comment.
Parse form body before Base64 decoding
The server claims to accept FinTS HTTP requests as application/x-www-form-urlencoded, but it always runs decodeBase64(body) on the raw body. For standard form payloads like msg=<base64>, this produces an invalid FinTS message and the request path returns 500, so form-encoded clients cannot use this mock endpoint. Extract and URL-decode the form field before Base64 decoding.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a new self-contained FinTS 3.0 mock HTTP server (packages/fints-test-server) intended to enable deterministic integration tests for PinTanClient without requiring a real bank connection.
Changes:
- Introduces
fints-test-serverpackage: FinTS protocol parsing, response message building, and a basic HTTP endpoint. - Implements a stateful request handler for core dialog flow and major business segments (HKSPA/HKSAL/HKKAZ/HKCDB/HKCCS/HKDSE/HKTAN/HKEND).
- Adds unit + integration tests for the protocol, message builder, request handler, and end-to-end client interactions.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updates dependency lockfile to include the new workspace package and related dependency graph changes. |
| packages/fints-test-server/package.json | Defines the new package metadata, scripts, and dependencies. |
| packages/fints-test-server/tsconfig.json | TypeScript compiler configuration for the new package. |
| packages/fints-test-server/jest.config.js | Jest/ts-jest configuration for the new package’s tests. |
| packages/fints-test-server/README.md | Documentation for using/configuring the test server. |
| packages/fints-test-server/src/index.ts | Public exports for server, handler, protocol helpers, and test-data helpers. |
| packages/fints-test-server/src/server.ts | HTTP POST server that decodes Base64 requests and returns Base64 responses. |
| packages/fints-test-server/src/request-handler.ts | Core FinTS message processing, dialog state, and segment handling. |
| packages/fints-test-server/src/message-builder.ts | FinTS envelope + segment builder utilities (HNHBK/HNVSK/HNVSD/HNHBS, HIRMG/HIRMS, etc.). |
| packages/fints-test-server/src/protocol.ts | FinTS parsing/escaping and Base64 encode/decode utilities. |
| packages/fints-test-server/src/test-data.ts | Default fixtures + MT940 generator for statements. |
| packages/fints-test-server/src/tests/test-protocol.ts | Unit tests for parser/encoding helpers. |
| packages/fints-test-server/src/tests/test-message-builder.ts | Unit tests for message construction. |
| packages/fints-test-server/src/tests/test-request-handler.ts | Unit tests for request handling + dialog flow. |
| packages/fints-test-server/src/tests/test-server-integration.ts | Integration tests running PinTanClient against the mock server. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Decode the Base64 request | ||
| const requestStr = decodeBase64(body); |
| ? [{ code: "0901", message: "*PIN gultig." }] | ||
| : [{ code: "9931", message: "PIN ungueltig." }]), | ||
| { code: "0020", message: "*Dialoginitialisierung erfolgreich" }, |
| // Count total inner segments (HNSHK + payload + HNSHA) | ||
| const totalInnerCount = 2 + innerSegments.length; // HNSHK + payload segments + HNSHA | ||
| const segCount = totalInnerCount + 1; // +1 because HNSHA segNo = 2 + innerSegments.length | ||
|
|
||
| // HNSHA: Signature footer | ||
| const hnsha = buildSegment("HNSHA", 2 + innerSegments.length + 1, 2, [ | ||
| formatNum(secRef), | ||
| ]); | ||
|
|
||
| // All content inside HNVSD | ||
| const innerContent = hnshk + innerSegments.join("") + hnsha; | ||
|
|
||
| // HNVSD: Encrypted data container | ||
| const hnvsd = buildSegment("HNVSD", 999, 1, [ | ||
| formatStringWithLength(innerContent), | ||
| ]); | ||
|
|
||
| // HNVSK: Encryption header | ||
| const hnvsk = buildSegment("HNVSK", 998, 3, [ | ||
| ["PIN", formatNum(profileVersion)], | ||
| formatNum(998), | ||
| formatNum(1), | ||
| ["1", "", systemId], | ||
| ["1", dateStr, timeStr], | ||
| ["2", "2", "13", formatStringWithLength("00000000"), "5", "1"], | ||
| [formatNum(COUNTRY_CODE), blz, escapeFinTS(userName), "V", formatNum(0), formatNum(0)], | ||
| formatNum(0), | ||
| ]); | ||
|
|
||
| // HNHBS: Message footer | ||
| const hnhbs = buildSegment("HNHBS", segCount + 2, 1, [ | ||
| formatNum(msgNo), | ||
| ]); |
| { code: "0010", message: "Nachricht entgegengenommen." }, | ||
| ])); | ||
| innerSegs.push(buildHIRMS(segNo++, [ | ||
| { code: "0030", message: "Auftrag empfangen - Loss bitte eine TAN ein" }, |
| "compilerOptions": { | ||
| "target": "ES2020", | ||
| "module": "commonjs", | ||
| "lib": ["ES2020"], | ||
| "declaration": true, | ||
| "outDir": "./dist", | ||
| "rootDir": "./src", | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "resolveJsonModule": true, | ||
| "skipLibCheck": true, | ||
| "forceConsistentCasingInFileNames": true, |
| }, | ||
| "files": [ | ||
| "dist", | ||
| "LICENSE", |
| ): string { | ||
| if (!isValidUser) { | ||
| return this.buildErrorMessage("0", userName, "0", profileVersion, msgNo, | ||
| "9931", "PIN ungueltig."); |
| /** Date of the transaction (yyyyMMdd) */ | ||
| date: string; | ||
| /** Booking date (yyyyMMdd) */ |
| "^.+\\.ts$": ["ts-jest", { | ||
| tsconfig: { | ||
| experimentalDecorators: true, | ||
| strict: false, |
Testing this library requires a real bank connection. This adds a self-contained FinTS 3.0 mock server (
packages/fints-test-server) that speaks the actual protocol over HTTP, enabling deterministic integration tests againstPinTanClient.Server implementation
protocol.ts— FinTS message parser/encoder (Base64, escape sequences,@length@binary data)message-builder.ts— Constructs spec-compliant response envelopes (HNHBK/HNVSK/HNVSD/HNHBS) with proper segment numbering and length calculationrequest-handler.ts— Stateful dialog session management, processes all major segments: HKSYN, HKIDN/HKVVB, HKSPA, HKSAL (v4-v7), HKKAZ (v4-v7 with MT940), HKCDB, HKCCS, HKDSE, HKTAN, HKENDtest-data.ts— Configurable fixtures (accounts, balances, MT940 transactions) with sensible defaultsserver.ts— HTTP POST endpoint accepting/returning Base64-encoded messages per FinTS transport specKey design decisions
accountNumber:sub:country:blz) and v7 (iban:bic:accountNumber:...) formatsrequireTan: true) to test challenge flows9931error codesUsage
Tests
PinTanClientend-to-end (accounts, balance, statements, standing orders, TAN flow, invalid PIN, custom config)