@lodashventure/medusa-login-provider 4.1.3 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/index.js +17 -1
- package/.medusa/server/src/providers/line/customer-helper.js +152 -0
- package/.medusa/server/src/providers/line/index.js +3 -2
- package/.medusa/server/src/providers/line/service.js +54 -76
- package/package.json +1 -1
- package/.medusa/server/src/providers/line/__tests__/line-api.mock.test.js +0 -472
- package/.medusa/server/src/providers/line/__tests__/service.test.js +0 -438
- package/.medusa/server/src/providers/line/__tests__/utils.test.js +0 -351
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const utils_1 = require("@medusajs/framework/utils");
|
|
7
|
-
const service_1 = __importDefault(require("../service"));
|
|
8
|
-
const utils_2 = require("../utils");
|
|
9
|
-
// Mock fetch and create a proper mock setup
|
|
10
|
-
const createMockFetch = () => {
|
|
11
|
-
const mockFetch = jest.fn();
|
|
12
|
-
global.fetch = mockFetch;
|
|
13
|
-
return mockFetch;
|
|
14
|
-
};
|
|
15
|
-
describe("LINE API Mock Tests", () => {
|
|
16
|
-
let mockFetch;
|
|
17
|
-
let service;
|
|
18
|
-
let rateLimiter;
|
|
19
|
-
let mockLogger;
|
|
20
|
-
const mockOptions = {
|
|
21
|
-
lineChannelId: "1234567890",
|
|
22
|
-
lineChannelSecret: "test-secret",
|
|
23
|
-
lineRedirectUrl: "https://example.com/callback",
|
|
24
|
-
};
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
jest.clearAllMocks();
|
|
27
|
-
mockFetch = createMockFetch();
|
|
28
|
-
mockLogger = {
|
|
29
|
-
info: jest.fn(),
|
|
30
|
-
error: jest.fn(),
|
|
31
|
-
warn: jest.fn(),
|
|
32
|
-
debug: jest.fn(),
|
|
33
|
-
};
|
|
34
|
-
service = new service_1.default({ logger: mockLogger }, mockOptions);
|
|
35
|
-
rateLimiter = new utils_2.RateLimiter(5, 60000); // 5 requests per minute
|
|
36
|
-
});
|
|
37
|
-
afterEach(() => {
|
|
38
|
-
jest.restoreAllMocks();
|
|
39
|
-
});
|
|
40
|
-
describe("LINE Token Exchange API", () => {
|
|
41
|
-
it("should successfully exchange authorization code for tokens", async () => {
|
|
42
|
-
const mockTokenResponse = {
|
|
43
|
-
access_token: "mock-access-token",
|
|
44
|
-
expires_in: 2592000,
|
|
45
|
-
id_token: "mock.id.token",
|
|
46
|
-
refresh_token: "mock-refresh-token",
|
|
47
|
-
scope: "profile openid email",
|
|
48
|
-
token_type: "Bearer",
|
|
49
|
-
};
|
|
50
|
-
mockFetch.mockResolvedValueOnce({
|
|
51
|
-
ok: true,
|
|
52
|
-
json: async () => mockTokenResponse,
|
|
53
|
-
});
|
|
54
|
-
const result = await service.exchangeCodeForTokens("authorization-code", "https://example.com/callback");
|
|
55
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
59
|
-
},
|
|
60
|
-
body: new URLSearchParams({
|
|
61
|
-
grant_type: "authorization_code",
|
|
62
|
-
code: "authorization-code",
|
|
63
|
-
client_id: "1234567890",
|
|
64
|
-
client_secret: "test-secret",
|
|
65
|
-
redirect_uri: "https://example.com/callback",
|
|
66
|
-
}),
|
|
67
|
-
});
|
|
68
|
-
expect(result).toEqual(mockTokenResponse);
|
|
69
|
-
});
|
|
70
|
-
it("should handle token exchange errors with detailed error messages", async () => {
|
|
71
|
-
const errorScenarios = [
|
|
72
|
-
{
|
|
73
|
-
status: 400,
|
|
74
|
-
errorText: "invalid_request",
|
|
75
|
-
expectedError: "Failed to exchange authorization code: 400 invalid_request",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
status: 401,
|
|
79
|
-
errorText: "invalid_client",
|
|
80
|
-
expectedError: "Failed to exchange authorization code: 401 invalid_client",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
status: 403,
|
|
84
|
-
errorText: "access_denied",
|
|
85
|
-
expectedError: "Failed to exchange authorization code: 403 access_denied",
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
for (const scenario of errorScenarios) {
|
|
89
|
-
mockFetch.mockResolvedValueOnce({
|
|
90
|
-
ok: false,
|
|
91
|
-
status: scenario.status,
|
|
92
|
-
text: async () => scenario.errorText,
|
|
93
|
-
});
|
|
94
|
-
await expect(service.exchangeCodeForTokens("invalid-code", "https://example.com/callback")).rejects.toThrow(new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, scenario.expectedError));
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
it("should handle network errors during token exchange", async () => {
|
|
98
|
-
mockFetch.mockRejectedValueOnce(new Error("Network error"));
|
|
99
|
-
await expect(service.exchangeCodeForTokens("code", "https://example.com/callback")).rejects.toThrow("Network error");
|
|
100
|
-
});
|
|
101
|
-
it("should validate request parameters", async () => {
|
|
102
|
-
mockFetch.mockResolvedValueOnce({
|
|
103
|
-
ok: true,
|
|
104
|
-
json: async () => ({}),
|
|
105
|
-
});
|
|
106
|
-
await service.exchangeCodeForTokens("test-code", "https://example.com/callback");
|
|
107
|
-
const expectedBody = new URLSearchParams({
|
|
108
|
-
grant_type: "authorization_code",
|
|
109
|
-
code: "test-code",
|
|
110
|
-
client_id: "1234567890",
|
|
111
|
-
client_secret: "test-secret",
|
|
112
|
-
redirect_uri: "https://example.com/callback",
|
|
113
|
-
});
|
|
114
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", expect.objectContaining({
|
|
115
|
-
body: expectedBody,
|
|
116
|
-
}));
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
describe("LINE Profile API", () => {
|
|
120
|
-
it("should successfully fetch user profile", async () => {
|
|
121
|
-
const mockProfile = {
|
|
122
|
-
userId: "U1234567890abcdefg",
|
|
123
|
-
displayName: "Test User",
|
|
124
|
-
pictureUrl: "https://obs.line-scdn.net/...",
|
|
125
|
-
statusMessage: "Hello World!",
|
|
126
|
-
};
|
|
127
|
-
mockFetch.mockResolvedValueOnce({
|
|
128
|
-
ok: true,
|
|
129
|
-
json: async () => mockProfile,
|
|
130
|
-
});
|
|
131
|
-
const result = await service.fetchUserProfile("valid-access-token");
|
|
132
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/v2/profile", {
|
|
133
|
-
headers: {
|
|
134
|
-
Authorization: "Bearer valid-access-token",
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
expect(result).toEqual(mockProfile);
|
|
138
|
-
});
|
|
139
|
-
it("should handle profile fetch errors", async () => {
|
|
140
|
-
const errorScenarios = [
|
|
141
|
-
{
|
|
142
|
-
status: 401,
|
|
143
|
-
errorText: "Invalid token",
|
|
144
|
-
description: "unauthorized token",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
status: 403,
|
|
148
|
-
errorText: "Forbidden",
|
|
149
|
-
description: "insufficient permissions",
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
status: 429,
|
|
153
|
-
errorText: "Too Many Requests",
|
|
154
|
-
description: "rate limit exceeded",
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
status: 500,
|
|
158
|
-
errorText: "Internal Server Error",
|
|
159
|
-
description: "server error",
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
for (const scenario of errorScenarios) {
|
|
163
|
-
mockFetch.mockResolvedValueOnce({
|
|
164
|
-
ok: false,
|
|
165
|
-
status: scenario.status,
|
|
166
|
-
text: async () => scenario.errorText,
|
|
167
|
-
});
|
|
168
|
-
await expect(service.fetchUserProfile("invalid-token")).rejects.toThrow(new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Failed to fetch LINE profile: ${scenario.status} ${scenario.errorText}`));
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
it("should handle malformed profile response", async () => {
|
|
172
|
-
mockFetch.mockResolvedValueOnce({
|
|
173
|
-
ok: true,
|
|
174
|
-
json: async () => {
|
|
175
|
-
throw new Error("Invalid JSON");
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
await expect(service.fetchUserProfile("valid-token")).rejects.toThrow("Invalid JSON");
|
|
179
|
-
});
|
|
180
|
-
it("should validate authorization header format", async () => {
|
|
181
|
-
mockFetch.mockResolvedValueOnce({
|
|
182
|
-
ok: true,
|
|
183
|
-
json: async () => ({}),
|
|
184
|
-
});
|
|
185
|
-
await service.fetchUserProfile("test-token");
|
|
186
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/v2/profile", {
|
|
187
|
-
headers: {
|
|
188
|
-
Authorization: "Bearer test-token",
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
describe("LINE Token Refresh API", () => {
|
|
194
|
-
it("should successfully refresh tokens", async () => {
|
|
195
|
-
const mockRefreshResponse = {
|
|
196
|
-
access_token: "new-access-token",
|
|
197
|
-
expires_in: 2592000,
|
|
198
|
-
refresh_token: "new-refresh-token",
|
|
199
|
-
token_type: "Bearer",
|
|
200
|
-
};
|
|
201
|
-
mockFetch.mockResolvedValueOnce({
|
|
202
|
-
ok: true,
|
|
203
|
-
json: async () => mockRefreshResponse,
|
|
204
|
-
});
|
|
205
|
-
const result = await service.refreshToken("valid-refresh-token");
|
|
206
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", {
|
|
207
|
-
method: "POST",
|
|
208
|
-
headers: {
|
|
209
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
210
|
-
},
|
|
211
|
-
body: new URLSearchParams({
|
|
212
|
-
grant_type: "refresh_token",
|
|
213
|
-
refresh_token: "valid-refresh-token",
|
|
214
|
-
client_id: "1234567890",
|
|
215
|
-
client_secret: "test-secret",
|
|
216
|
-
}),
|
|
217
|
-
});
|
|
218
|
-
expect(result).toEqual(mockRefreshResponse);
|
|
219
|
-
});
|
|
220
|
-
it("should handle refresh token errors gracefully", async () => {
|
|
221
|
-
const errorCases = [
|
|
222
|
-
{ status: 400, description: "invalid_request" },
|
|
223
|
-
{ status: 401, description: "invalid_token" },
|
|
224
|
-
{ status: 403, description: "access_denied" },
|
|
225
|
-
];
|
|
226
|
-
for (const errorCase of errorCases) {
|
|
227
|
-
mockFetch.mockResolvedValueOnce({
|
|
228
|
-
ok: false,
|
|
229
|
-
status: errorCase.status,
|
|
230
|
-
});
|
|
231
|
-
const result = await service.refreshToken("invalid-refresh-token");
|
|
232
|
-
expect(result).toBeNull();
|
|
233
|
-
expect(mockLogger.error).toHaveBeenCalledWith(`Failed to refresh LINE token: ${errorCase.status}`);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
it("should handle network errors during token refresh", async () => {
|
|
237
|
-
mockFetch.mockRejectedValueOnce(new Error("Connection timeout"));
|
|
238
|
-
const result = await service.refreshToken("valid-refresh-token");
|
|
239
|
-
expect(result).toBeNull();
|
|
240
|
-
expect(mockLogger.error).toHaveBeenCalledWith("Error refreshing LINE token:", expect.any(Error));
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
describe("LINE Token Revocation API", () => {
|
|
244
|
-
it("should successfully revoke tokens", async () => {
|
|
245
|
-
mockFetch.mockResolvedValueOnce({
|
|
246
|
-
ok: true,
|
|
247
|
-
});
|
|
248
|
-
const result = await service.revokeToken("access-token-to-revoke");
|
|
249
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/revoke", {
|
|
250
|
-
method: "POST",
|
|
251
|
-
headers: {
|
|
252
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
253
|
-
},
|
|
254
|
-
body: new URLSearchParams({
|
|
255
|
-
access_token: "access-token-to-revoke",
|
|
256
|
-
client_id: "1234567890",
|
|
257
|
-
client_secret: "test-secret",
|
|
258
|
-
}),
|
|
259
|
-
});
|
|
260
|
-
expect(result).toBe(true);
|
|
261
|
-
});
|
|
262
|
-
it("should handle token revocation failures", async () => {
|
|
263
|
-
mockFetch.mockResolvedValueOnce({
|
|
264
|
-
ok: false,
|
|
265
|
-
status: 400,
|
|
266
|
-
});
|
|
267
|
-
const result = await service.revokeToken("invalid-token");
|
|
268
|
-
expect(result).toBe(false);
|
|
269
|
-
});
|
|
270
|
-
it("should handle network errors during token revocation", async () => {
|
|
271
|
-
mockFetch.mockRejectedValueOnce(new Error("Network error"));
|
|
272
|
-
const result = await service.revokeToken("access-token");
|
|
273
|
-
expect(result).toBe(false);
|
|
274
|
-
expect(mockLogger.error).toHaveBeenCalledWith("Error revoking LINE token:", expect.any(Error));
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
describe("Rate Limiting with LINE APIs", () => {
|
|
278
|
-
beforeEach(() => {
|
|
279
|
-
jest.spyOn(Date, "now").mockReturnValue(1000000);
|
|
280
|
-
});
|
|
281
|
-
afterEach(() => {
|
|
282
|
-
Date.now.mockRestore();
|
|
283
|
-
});
|
|
284
|
-
it("should respect rate limits for token refresh", async () => {
|
|
285
|
-
const userId = "user123";
|
|
286
|
-
// Allow first few requests
|
|
287
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
288
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
289
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
290
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
291
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
292
|
-
// Sixth request should be blocked
|
|
293
|
-
expect(rateLimiter.isAllowed(userId)).toBe(false);
|
|
294
|
-
});
|
|
295
|
-
it("should handle rate limit responses from LINE API", async () => {
|
|
296
|
-
mockFetch.mockResolvedValueOnce({
|
|
297
|
-
ok: false,
|
|
298
|
-
status: 429,
|
|
299
|
-
text: async () => "Too Many Requests",
|
|
300
|
-
});
|
|
301
|
-
await expect(service.fetchUserProfile("token")).rejects.toThrow(expect.objectContaining({
|
|
302
|
-
message: expect.stringContaining("Failed to fetch LINE profile: 429")
|
|
303
|
-
}));
|
|
304
|
-
});
|
|
305
|
-
it("should implement exponential backoff for rate limited requests", async () => {
|
|
306
|
-
const userId = "rate-limited-user";
|
|
307
|
-
// Simulate rate limit reached
|
|
308
|
-
for (let i = 0; i < 5; i++) {
|
|
309
|
-
rateLimiter.isAllowed(userId);
|
|
310
|
-
}
|
|
311
|
-
expect(rateLimiter.isAllowed(userId)).toBe(false);
|
|
312
|
-
// Simulate time passing (1 minute + 1 second)
|
|
313
|
-
Date.now.mockReturnValue(1000000 + 60001);
|
|
314
|
-
// Should be allowed again
|
|
315
|
-
expect(rateLimiter.isAllowed(userId)).toBe(true);
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
describe("LINE API Error Handling", () => {
|
|
319
|
-
it("should map LINE API errors to appropriate MedusaErrors", async () => {
|
|
320
|
-
const errorMappings = [
|
|
321
|
-
{ status: 400, expectedMessage: "Invalid request to LINE API" },
|
|
322
|
-
{ status: 401, expectedMessage: "LINE authentication failed" },
|
|
323
|
-
{ status: 403, expectedMessage: "Access forbidden by LINE" },
|
|
324
|
-
{ status: 429, expectedMessage: "Too many requests to LINE API" },
|
|
325
|
-
{ status: 500, expectedMessage: "LINE server error" },
|
|
326
|
-
{ status: 503, expectedMessage: "LINE service temporarily unavailable" },
|
|
327
|
-
];
|
|
328
|
-
errorMappings.forEach(({ status, expectedMessage }) => {
|
|
329
|
-
expect(() => {
|
|
330
|
-
(0, utils_2.handleLineApiError)(status, "Test error message");
|
|
331
|
-
}).toThrow(new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `${expectedMessage}: Test error message`));
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
it("should handle unknown LINE API errors", async () => {
|
|
335
|
-
expect(() => {
|
|
336
|
-
(0, utils_2.handleLineApiError)(418, "I'm a teapot");
|
|
337
|
-
}).toThrow(new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "LINE API error: 418: I'm a teapot"));
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
describe("LINE API Response Validation", () => {
|
|
341
|
-
it("should validate token response structure", async () => {
|
|
342
|
-
const validTokenResponse = {
|
|
343
|
-
access_token: "valid-token",
|
|
344
|
-
expires_in: 2592000,
|
|
345
|
-
id_token: "valid.id.token",
|
|
346
|
-
refresh_token: "valid-refresh",
|
|
347
|
-
scope: "profile openid",
|
|
348
|
-
token_type: "Bearer",
|
|
349
|
-
};
|
|
350
|
-
mockFetch.mockResolvedValueOnce({
|
|
351
|
-
ok: true,
|
|
352
|
-
json: async () => validTokenResponse,
|
|
353
|
-
});
|
|
354
|
-
const result = await service.exchangeCodeForTokens("code", "https://example.com/callback");
|
|
355
|
-
expect(result).toMatchObject({
|
|
356
|
-
access_token: expect.any(String),
|
|
357
|
-
expires_in: expect.any(Number),
|
|
358
|
-
id_token: expect.any(String),
|
|
359
|
-
refresh_token: expect.any(String),
|
|
360
|
-
scope: expect.any(String),
|
|
361
|
-
token_type: expect.any(String),
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
it("should validate profile response structure", async () => {
|
|
365
|
-
const validProfileResponse = {
|
|
366
|
-
userId: "U1234567890",
|
|
367
|
-
displayName: "Test User",
|
|
368
|
-
pictureUrl: "https://example.com/pic.jpg",
|
|
369
|
-
statusMessage: "Hello",
|
|
370
|
-
};
|
|
371
|
-
mockFetch.mockResolvedValueOnce({
|
|
372
|
-
ok: true,
|
|
373
|
-
json: async () => validProfileResponse,
|
|
374
|
-
});
|
|
375
|
-
const result = await service.fetchUserProfile("token");
|
|
376
|
-
expect(result).toMatchObject({
|
|
377
|
-
userId: expect.any(String),
|
|
378
|
-
displayName: expect.any(String),
|
|
379
|
-
pictureUrl: expect.any(String),
|
|
380
|
-
statusMessage: expect.any(String),
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
it("should handle incomplete or malformed responses", async () => {
|
|
384
|
-
const incompleteResponses = [
|
|
385
|
-
{}, // Empty response
|
|
386
|
-
{ userId: "U123" }, // Missing required fields
|
|
387
|
-
{ displayName: "Test" }, // Missing userId
|
|
388
|
-
null, // Null response
|
|
389
|
-
];
|
|
390
|
-
for (const response of incompleteResponses) {
|
|
391
|
-
mockFetch.mockResolvedValueOnce({
|
|
392
|
-
ok: true,
|
|
393
|
-
json: async () => response,
|
|
394
|
-
});
|
|
395
|
-
const result = await service.fetchUserProfile("token");
|
|
396
|
-
// Should still return the response, but application should handle validation
|
|
397
|
-
expect(result).toEqual(response);
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
describe("LINE API Authentication Headers", () => {
|
|
402
|
-
it("should send correct authorization headers for profile requests", async () => {
|
|
403
|
-
mockFetch.mockResolvedValueOnce({
|
|
404
|
-
ok: true,
|
|
405
|
-
json: async () => ({}),
|
|
406
|
-
});
|
|
407
|
-
await service.fetchUserProfile("test-access-token");
|
|
408
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/v2/profile", {
|
|
409
|
-
headers: {
|
|
410
|
-
Authorization: "Bearer test-access-token",
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
it("should send correct content-type headers for token requests", async () => {
|
|
415
|
-
mockFetch.mockResolvedValueOnce({
|
|
416
|
-
ok: true,
|
|
417
|
-
json: async () => ({}),
|
|
418
|
-
});
|
|
419
|
-
await service.exchangeCodeForTokens("code", "callback");
|
|
420
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", {
|
|
421
|
-
method: "POST",
|
|
422
|
-
headers: {
|
|
423
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
424
|
-
},
|
|
425
|
-
body: expect.any(URLSearchParams),
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
describe("LINE API Timeout Handling", () => {
|
|
430
|
-
it("should handle request timeouts gracefully", async () => {
|
|
431
|
-
// Simulate timeout by rejecting with timeout error
|
|
432
|
-
mockFetch.mockRejectedValueOnce(new Error("Request timeout"));
|
|
433
|
-
const result = await service.refreshToken("token");
|
|
434
|
-
expect(result).toBeNull();
|
|
435
|
-
expect(mockLogger.error).toHaveBeenCalledWith("Error refreshing LINE token:", expect.any(Error));
|
|
436
|
-
});
|
|
437
|
-
it("should handle slow responses appropriately", async () => {
|
|
438
|
-
// Simulate slow response
|
|
439
|
-
mockFetch.mockImplementationOnce(() => new Promise((resolve) => {
|
|
440
|
-
setTimeout(() => {
|
|
441
|
-
resolve({
|
|
442
|
-
ok: true,
|
|
443
|
-
json: async () => ({ access_token: "token" }),
|
|
444
|
-
});
|
|
445
|
-
}, 100);
|
|
446
|
-
}));
|
|
447
|
-
const result = await service.refreshToken("token");
|
|
448
|
-
expect(result).toEqual({ access_token: "token" });
|
|
449
|
-
});
|
|
450
|
-
});
|
|
451
|
-
describe("LINE API URL Validation", () => {
|
|
452
|
-
it("should use correct LINE API endpoints", async () => {
|
|
453
|
-
mockFetch.mockResolvedValue({
|
|
454
|
-
ok: true,
|
|
455
|
-
json: async () => ({}),
|
|
456
|
-
});
|
|
457
|
-
// Test token endpoint
|
|
458
|
-
await service.exchangeCodeForTokens("code", "callback");
|
|
459
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", expect.any(Object));
|
|
460
|
-
// Test profile endpoint
|
|
461
|
-
await service.fetchUserProfile("token");
|
|
462
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/v2/profile", expect.any(Object));
|
|
463
|
-
// Test refresh endpoint (same as token endpoint)
|
|
464
|
-
await service.refreshToken("token");
|
|
465
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/token", expect.any(Object));
|
|
466
|
-
// Test revoke endpoint
|
|
467
|
-
await service.revokeToken("token");
|
|
468
|
-
expect(mockFetch).toHaveBeenCalledWith("https://api.line.me/oauth2/v2.1/revoke", expect.any(Object));
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGluZS1hcGkubW9jay50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL3Byb3ZpZGVycy9saW5lL19fdGVzdHNfXy9saW5lLWFwaS5tb2NrLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSxxREFBd0Q7QUFDeEQseURBQTZDO0FBQzdDLG9DQUEyRDtBQUUzRCw0Q0FBNEM7QUFDNUMsTUFBTSxlQUFlLEdBQUcsR0FBRyxFQUFFO0lBQzNCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztJQUM1QixNQUFNLENBQUMsS0FBSyxHQUFHLFNBQVMsQ0FBQztJQUN6QixPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDLENBQUM7QUFFRixRQUFRLENBQUMscUJBQXFCLEVBQUUsR0FBRyxFQUFFO0lBQ25DLElBQUksU0FBNEMsQ0FBQztJQUNqRCxJQUFJLE9BQTRCLENBQUM7SUFDakMsSUFBSSxXQUF3QixDQUFDO0lBQzdCLElBQUksVUFBZSxDQUFDO0lBRXBCLE1BQU0sV0FBVyxHQUFHO1FBQ2xCLGFBQWEsRUFBRSxZQUFZO1FBQzNCLGlCQUFpQixFQUFFLGFBQWE7UUFDaEMsZUFBZSxFQUFFLDhCQUE4QjtLQUNoRCxDQUFDO0lBRUYsVUFBVSxDQUFDLEdBQUcsRUFBRTtRQUNkLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNyQixTQUFTLEdBQUcsZUFBZSxFQUFFLENBQUM7UUFFOUIsVUFBVSxHQUFHO1lBQ1gsSUFBSSxFQUFFLElBQUksQ0FBQyxFQUFFLEVBQUU7WUFDZixLQUFLLEVBQUUsSUFBSSxDQUFDLEVBQUUsRUFBRTtZQUNoQixJQUFJLEVBQUUsSUFBSSxDQUFDLEVBQUUsRUFBRTtZQUNmLEtBQUssRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFO1NBQ2pCLENBQUM7UUFFRixPQUFPLEdBQUcsSUFBSSxpQkFBbUIsQ0FDL0IsRUFBRSxNQUFNLEVBQUUsVUFB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
|