Skip to content

Commit 95510ec

Browse files
authored
Merge pull request #31 from larsdecker/copilot/add-decoupled-tan-support
Implement Decoupled TAN (Asynchronous Authentication) Support
2 parents 2c4cf95 + 5ad9493 commit 95510ec

13 files changed

Lines changed: 2926 additions & 1451 deletions

packages/fints/src/__tests__/test-tan-required-error.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,31 @@ describe("tan-required-error", () => {
188188
expect(error.getStepDescription()).toBe("TAN process completed");
189189
});
190190
});
191+
192+
describe("isDecoupledTan", () => {
193+
test("should return false when decoupledTanState is not set", () => {
194+
const error = new TanRequiredError(
195+
"TAN required",
196+
"ref123",
197+
"Please enter TAN",
198+
Buffer.from(""),
199+
mockDialog,
200+
);
201+
202+
expect(error.isDecoupledTan()).toBe(false);
203+
});
204+
205+
test("should return true when decoupledTanState is set", () => {
206+
const error = new TanRequiredError(
207+
"TAN required",
208+
"ref123",
209+
"Please enter TAN",
210+
Buffer.from(""),
211+
mockDialog,
212+
);
213+
error.decoupledTanState = "initiated" as any;
214+
215+
expect(error.isDecoupledTan()).toBe(true);
216+
});
217+
});
191218
});
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { DecoupledTanError } from "../../errors/decoupled-tan-error";
2+
import { DecoupledTanState, DecoupledTanStatus } from "../types";
3+
4+
describe("DecoupledTanError", () => {
5+
let mockStatus: DecoupledTanStatus;
6+
7+
beforeEach(() => {
8+
mockStatus = {
9+
state: DecoupledTanState.FAILED,
10+
transactionReference: "ref123",
11+
challengeText: "Please confirm",
12+
statusRequestCount: 5,
13+
maxStatusRequests: 60,
14+
startTime: new Date(),
15+
errorMessage: "Test error",
16+
};
17+
});
18+
19+
describe("Constructor", () => {
20+
test("should create error with status information", () => {
21+
const error = new DecoupledTanError("Test error", mockStatus);
22+
23+
expect(error.message).toBe("Test error");
24+
expect(error.state).toBe(DecoupledTanState.FAILED);
25+
expect(error.transactionReference).toBe("ref123");
26+
expect(error.remainingStatusRequests).toBe(55);
27+
expect(error.status).toEqual(mockStatus);
28+
});
29+
30+
test("should include return code if provided", () => {
31+
const error = new DecoupledTanError("Test error", mockStatus, "9999");
32+
33+
expect(error.returnCode).toBe("9999");
34+
});
35+
36+
test("should have correct error name", () => {
37+
const error = new DecoupledTanError("Test error", mockStatus);
38+
39+
expect(error.name).toBe("DecoupledTanError");
40+
});
41+
});
42+
43+
describe("getUserMessage", () => {
44+
test("should return user-friendly message for TIMED_OUT state", () => {
45+
mockStatus.state = DecoupledTanState.TIMED_OUT;
46+
const error = new DecoupledTanError("Timeout", mockStatus);
47+
48+
const message = error.getUserMessage();
49+
50+
expect(message).toContain("timed out");
51+
expect(message).toContain("ref123");
52+
});
53+
54+
test("should return user-friendly message for CANCELLED state", () => {
55+
mockStatus.state = DecoupledTanState.CANCELLED;
56+
const error = new DecoupledTanError("Cancelled", mockStatus);
57+
58+
const message = error.getUserMessage();
59+
60+
expect(message).toContain("cancelled");
61+
expect(message).toContain("ref123");
62+
});
63+
64+
test("should return user-friendly message for FAILED state", () => {
65+
mockStatus.state = DecoupledTanState.FAILED;
66+
const error = new DecoupledTanError("Failed", mockStatus);
67+
68+
const message = error.getUserMessage();
69+
70+
expect(message).toContain("failed");
71+
expect(message).toContain("Failed");
72+
expect(message).toContain("ref123");
73+
});
74+
75+
test("should return generic message for unknown state", () => {
76+
mockStatus.state = DecoupledTanState.INITIATED;
77+
const error = new DecoupledTanError("Error", mockStatus);
78+
79+
const message = error.getUserMessage();
80+
81+
expect(message).toContain("initiated");
82+
expect(message).toContain("Error");
83+
});
84+
});
85+
86+
describe("Helper methods", () => {
87+
test("isTimeout should return true for TIMED_OUT state", () => {
88+
mockStatus.state = DecoupledTanState.TIMED_OUT;
89+
const error = new DecoupledTanError("Timeout", mockStatus);
90+
91+
expect(error.isTimeout()).toBe(true);
92+
});
93+
94+
test("isTimeout should return false for other states", () => {
95+
mockStatus.state = DecoupledTanState.FAILED;
96+
const error = new DecoupledTanError("Error", mockStatus);
97+
98+
expect(error.isTimeout()).toBe(false);
99+
});
100+
101+
test("isCancelled should return true for CANCELLED state", () => {
102+
mockStatus.state = DecoupledTanState.CANCELLED;
103+
const error = new DecoupledTanError("Cancelled", mockStatus);
104+
105+
expect(error.isCancelled()).toBe(true);
106+
});
107+
108+
test("isCancelled should return false for other states", () => {
109+
mockStatus.state = DecoupledTanState.FAILED;
110+
const error = new DecoupledTanError("Error", mockStatus);
111+
112+
expect(error.isCancelled()).toBe(false);
113+
});
114+
115+
test("hasRemainingRequests should return true when requests remain", () => {
116+
mockStatus.statusRequestCount = 10;
117+
mockStatus.maxStatusRequests = 60;
118+
const error = new DecoupledTanError("Error", mockStatus);
119+
120+
expect(error.hasRemainingRequests()).toBe(true);
121+
expect(error.remainingStatusRequests).toBe(50);
122+
});
123+
124+
test("hasRemainingRequests should return false when no requests remain", () => {
125+
mockStatus.statusRequestCount = 60;
126+
mockStatus.maxStatusRequests = 60;
127+
const error = new DecoupledTanError("Error", mockStatus);
128+
129+
expect(error.hasRemainingRequests()).toBe(false);
130+
expect(error.remainingStatusRequests).toBe(0);
131+
});
132+
});
133+
});

0 commit comments

Comments
 (0)