@jolibox/implement 1.2.4 → 1.2.5
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/.rush/temp/package-deps_build.json +24 -18
- package/dist/common/cache/__tests__/request-cache-service.test.d.ts +1 -0
- package/dist/common/cache/request-cache-service.d.ts +111 -0
- package/dist/common/report/base-tracker.d.ts +2 -1
- package/dist/common/rewards/cached-fetch-reward.d.ts +46 -0
- package/dist/common/rewards/cached-reward-service.d.ts +24 -0
- package/dist/common/rewards/fetch-reward.d.ts +2 -3
- package/dist/common/rewards/index.d.ts +2 -0
- package/dist/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.d.ts +34 -0
- package/dist/common/rewards/registers/utils/coins/jolicoin/fetch-balance.d.ts +2 -1
- package/dist/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.d.ts +34 -0
- package/dist/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.d.ts +2 -1
- package/dist/index.js +9 -9
- package/dist/index.native.js +33 -33
- package/dist/native/payment/payment-service.d.ts +36 -30
- package/implement.build.log +2 -2
- package/package.json +5 -5
- package/src/common/cache/__tests__/request-cache-service.test.ts +686 -0
- package/src/common/cache/request-cache-service.ts +393 -0
- package/src/common/report/base-tracker.ts +2 -2
- package/src/common/rewards/cached-fetch-reward.ts +241 -0
- package/src/common/rewards/cached-reward-service.ts +255 -0
- package/src/common/rewards/fetch-reward.ts +17 -93
- package/src/common/rewards/index.ts +3 -0
- package/src/common/rewards/registers/utils/coins/commands/use-payment.ts +8 -0
- package/src/common/rewards/registers/utils/coins/jolicoin/cached-fetch-balance.ts +177 -0
- package/src/common/rewards/registers/utils/coins/jolicoin/fetch-balance.ts +13 -1
- package/src/common/rewards/registers/utils/coins/jolicoin/jolicoin-handler.ts +2 -0
- package/src/common/rewards/registers/utils/coins/joligem/cached-fetch-gem-balance.ts +181 -0
- package/src/common/rewards/registers/utils/coins/joligem/fetch-gem-balance.ts +13 -1
- package/src/common/rewards/registers/utils/coins/joligem/gem-handler.ts +2 -0
- package/src/h5/api/ads.ts +5 -0
- package/src/h5/api/storage.ts +2 -2
- package/src/h5/http/index.ts +2 -2
- package/src/h5/report/event-tracker.ts +2 -2
- package/src/native/api/ads.ts +7 -1
- package/src/native/api/payment.ts +4 -4
- package/src/native/payment/__tests__/payment-service-simple.test.ts +97 -31
- package/src/native/payment/payment-service.ts +224 -210
- package/src/native/rewards/ui/payment-modal.ts +14 -7
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { RequestCacheService, RequestAdapter } from '@/common/cache/request-cache-service';
|
|
2
|
+
import { IHttpClient } from '@/common/http';
|
|
3
|
+
import { StandardResponse } from '@jolibox/types';
|
|
4
|
+
|
|
5
|
+
interface GemBalanceRequest {
|
|
6
|
+
endpoint: string;
|
|
7
|
+
query?: Record<string, unknown>;
|
|
8
|
+
method?: 'GET' | 'POST';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 定义宝石余额响应类型
|
|
12
|
+
interface GemBalanceResponse {
|
|
13
|
+
balance: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface GemBalanceCacheData {
|
|
17
|
+
balance: number;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class GemBalanceRequestAdapter
|
|
22
|
+
implements
|
|
23
|
+
RequestAdapter<GemBalanceRequest, StandardResponse<GemBalanceResponse>, GemBalanceCacheData, undefined>
|
|
24
|
+
{
|
|
25
|
+
constructor(private httpClient: IHttpClient) {}
|
|
26
|
+
|
|
27
|
+
async execute(
|
|
28
|
+
endpoint: string,
|
|
29
|
+
options?: GemBalanceRequest
|
|
30
|
+
): Promise<StandardResponse<GemBalanceResponse>> {
|
|
31
|
+
const method = options?.method || 'GET';
|
|
32
|
+
const query = options?.query || {};
|
|
33
|
+
|
|
34
|
+
if (method === 'GET') {
|
|
35
|
+
return await this.httpClient.get<StandardResponse<GemBalanceResponse>>(endpoint, {
|
|
36
|
+
query: query as Record<string, string>
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
return await this.httpClient.post<StandardResponse<GemBalanceResponse>>(endpoint, {
|
|
40
|
+
query: query as Record<string, string>
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
extractCacheableData(response: StandardResponse<GemBalanceResponse>): GemBalanceCacheData {
|
|
46
|
+
return {
|
|
47
|
+
balance: response.data?.balance || 0,
|
|
48
|
+
timestamp: Date.now()
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 宝石余额使用纯缓存模式
|
|
53
|
+
extractRealTimeData?: undefined;
|
|
54
|
+
mergeData?: undefined;
|
|
55
|
+
|
|
56
|
+
processCachedData(cached: GemBalanceCacheData): StandardResponse<GemBalanceResponse> {
|
|
57
|
+
return {
|
|
58
|
+
code: 'SUCCESS',
|
|
59
|
+
message: 'success from cache',
|
|
60
|
+
data: {
|
|
61
|
+
balance: cached.balance
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 缓存宝石余额服务
|
|
68
|
+
export class CachedGemBalanceService {
|
|
69
|
+
private gemBalanceService: RequestCacheService<
|
|
70
|
+
GemBalanceRequest,
|
|
71
|
+
StandardResponse<GemBalanceResponse>,
|
|
72
|
+
GemBalanceCacheData,
|
|
73
|
+
undefined
|
|
74
|
+
>;
|
|
75
|
+
|
|
76
|
+
constructor(httpClient: IHttpClient) {
|
|
77
|
+
this.gemBalanceService = new (RequestCacheService as new (...args: unknown[]) => RequestCacheService<
|
|
78
|
+
GemBalanceRequest,
|
|
79
|
+
StandardResponse<GemBalanceResponse>,
|
|
80
|
+
GemBalanceCacheData,
|
|
81
|
+
undefined
|
|
82
|
+
>)(new GemBalanceRequestAdapter(httpClient), {
|
|
83
|
+
duration: 20 * 60 * 1000, // 20分钟缓存(宝石余额变化较少)
|
|
84
|
+
timeout: 1000, // 1s timeout for request
|
|
85
|
+
keyGenerator: (endpoint: string, params?: GemBalanceRequest) => {
|
|
86
|
+
const queryStr = params?.query ? JSON.stringify(params.query) : '';
|
|
87
|
+
return `${endpoint}:${queryStr}`;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getGemBalance(type = 'GEM'): Promise<GemBalanceResponse | undefined> {
|
|
93
|
+
const response = await this.gemBalanceService.request('/api/joli-gem/balance', {
|
|
94
|
+
endpoint: '/api/joli-gem/balance',
|
|
95
|
+
query: { type },
|
|
96
|
+
method: 'GET'
|
|
97
|
+
});
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async refreshGemBalance(type = 'GEM'): Promise<GemBalanceResponse | undefined> {
|
|
102
|
+
const response = await this.gemBalanceService.forceRequest('/api/joli-gem/balance', {
|
|
103
|
+
endpoint: '/api/joli-gem/balance',
|
|
104
|
+
query: { type },
|
|
105
|
+
method: 'GET'
|
|
106
|
+
});
|
|
107
|
+
return response.data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async warmupGemBalance(type = 'GEM'): Promise<void> {
|
|
111
|
+
await this.gemBalanceService.warmupCache('/api/joli-gem/balance', {
|
|
112
|
+
endpoint: '/api/joli-gem/balance',
|
|
113
|
+
query: { type },
|
|
114
|
+
method: 'GET'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
clearGemBalanceCache(): void {
|
|
119
|
+
this.gemBalanceService.clearCache();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getGemBalanceCacheStats() {
|
|
123
|
+
return this.gemBalanceService.getCacheStats();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
clearExpiredCache(): void {
|
|
127
|
+
this.gemBalanceService.clearExpiredCache();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// create instance
|
|
132
|
+
let cachedGemBalanceServiceInstance: CachedGemBalanceService | null = null;
|
|
133
|
+
|
|
134
|
+
function getCachedGemBalanceService(httpClient: IHttpClient): CachedGemBalanceService {
|
|
135
|
+
if (!cachedGemBalanceServiceInstance) {
|
|
136
|
+
cachedGemBalanceServiceInstance = new CachedGemBalanceService(httpClient);
|
|
137
|
+
}
|
|
138
|
+
return cachedGemBalanceServiceInstance;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 缓存版本的 fetchGemBalance 函数
|
|
143
|
+
*/
|
|
144
|
+
export const fetchGemBalanceCached = async (httpClient: IHttpClient, type = 'GEM') => {
|
|
145
|
+
const cachedGemBalanceService = getCachedGemBalanceService(httpClient);
|
|
146
|
+
return await cachedGemBalanceService.getGemBalance(type);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 强制刷新宝石余额缓存
|
|
151
|
+
*/
|
|
152
|
+
export const refreshGemBalanceCache = async (httpClient: IHttpClient, type = 'GEM') => {
|
|
153
|
+
const cachedGemBalanceService = getCachedGemBalanceService(httpClient);
|
|
154
|
+
const result = await cachedGemBalanceService.refreshGemBalance(type);
|
|
155
|
+
console.log(`Gem balance cache refreshed for type: ${type}`);
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 预热宝石余额缓存
|
|
161
|
+
*/
|
|
162
|
+
export const warmupGemBalanceCache = async (httpClient: IHttpClient, type = 'GEM') => {
|
|
163
|
+
const cachedGemBalanceService = getCachedGemBalanceService(httpClient);
|
|
164
|
+
await cachedGemBalanceService.warmupGemBalance(type);
|
|
165
|
+
console.log(`Gem balance cache warmed up for type: ${type}`);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const clearGemBalanceCache = (httpClient: IHttpClient) => {
|
|
169
|
+
const cachedGemBalanceService = getCachedGemBalanceService(httpClient);
|
|
170
|
+
cachedGemBalanceService.clearGemBalanceCache();
|
|
171
|
+
console.log('Gem balance cache cleared');
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const getGemBalanceCacheStats = (httpClient: IHttpClient) => {
|
|
175
|
+
const cachedGemBalanceService = getCachedGemBalanceService(httpClient);
|
|
176
|
+
return cachedGemBalanceService.getGemBalanceCacheStats();
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const getGemBalanceCacheService = (httpClient: IHttpClient): CachedGemBalanceService => {
|
|
180
|
+
return getCachedGemBalanceService(httpClient);
|
|
181
|
+
};
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { IHttpClient } from '@/common/http';
|
|
2
2
|
import { StandardResponse } from '@jolibox/types';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
// export cached version as default implementation
|
|
5
|
+
export {
|
|
6
|
+
fetchGemBalanceCached as fetchGemBalance,
|
|
7
|
+
// 提供缓存管理功能
|
|
8
|
+
refreshGemBalanceCache,
|
|
9
|
+
warmupGemBalanceCache,
|
|
10
|
+
clearGemBalanceCache,
|
|
11
|
+
getGemBalanceCacheStats,
|
|
12
|
+
getGemBalanceCacheService
|
|
13
|
+
} from './cached-fetch-gem-balance';
|
|
14
|
+
|
|
15
|
+
// original version
|
|
16
|
+
export const fetchGemBalanceNonCached = async (httpClient: IHttpClient) => {
|
|
5
17
|
const res = await httpClient.get<
|
|
6
18
|
StandardResponse<{
|
|
7
19
|
balance: number;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { registerUseJolicoinCommand, createShowUnlockWithCurrencyModal } from '../commands/use-jolicoin';
|
|
16
16
|
import { rewardsCommands } from '../rewards-command';
|
|
17
17
|
import { RewardsCommandType } from '@jolibox/types';
|
|
18
|
+
import { refreshGemBalanceCache, warmupGemBalanceCache } from './cached-fetch-gem-balance';
|
|
18
19
|
|
|
19
20
|
interface IGemUnlockRes {
|
|
20
21
|
code: 'SUCCESS' | 'BALANCE_NOT_ENOUGH' | 'EPISODE_LOCK_JUMP' | 'EPISODE_UNLOCK_ALREADY';
|
|
@@ -103,6 +104,7 @@ export const createCommonGemRewardHandler = (
|
|
|
103
104
|
if ('adViewed' in params) {
|
|
104
105
|
params.adViewed?.();
|
|
105
106
|
}
|
|
107
|
+
refreshGemBalanceCache(httpClient);
|
|
106
108
|
} catch (e) {
|
|
107
109
|
console.error('-----unlockWithGem adBreakDone error-----', e);
|
|
108
110
|
}
|
package/src/h5/api/ads.ts
CHANGED
|
@@ -23,6 +23,8 @@ import HuaweiQuickAdsHandler from './platformAdsHandle/HuaweiQuickAdsHandler';
|
|
|
23
23
|
import GamedistributionAdsHandler from './platformAdsHandle/GamedistributionAdsHandler';
|
|
24
24
|
import FunmaxAdsHandler from './platformAdsHandle/FunmaxAdsHandler';
|
|
25
25
|
import JoliboxAdsHandler from './platformAdsHandle/JoliboxAdsHandler';
|
|
26
|
+
import { warmupRewardCache } from '@/common/rewards/fetch-reward';
|
|
27
|
+
import { warmupBalanceCache, warmupGemBalanceCache } from '@/common/rewards';
|
|
26
28
|
|
|
27
29
|
declare global {
|
|
28
30
|
interface Window {
|
|
@@ -154,6 +156,9 @@ rewardsHelper.registerRewardHandler(
|
|
|
154
156
|
}) as unknown as (params?: unknown) => Promise<boolean>
|
|
155
157
|
);
|
|
156
158
|
|
|
159
|
+
warmupRewardCache(httpClient);
|
|
160
|
+
warmupBalanceCache(httpClient);
|
|
161
|
+
|
|
157
162
|
adEventEmitter.on('isAdShowing', (isAdShowing) => {
|
|
158
163
|
notifyCustomEvent('JOLIBOX_ADS_EVENT', { isAdShowing });
|
|
159
164
|
});
|
package/src/h5/api/storage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAPI, t, registerCanIUse } from './base';
|
|
2
|
-
import { createCommands,
|
|
2
|
+
import { createCommands, getH5ApiHost } from '@jolibox/common';
|
|
3
3
|
import { httpClientManager } from '@/h5/http';
|
|
4
4
|
import { context } from '@/common/context';
|
|
5
5
|
import { StorageResponse } from '@jolibox/types';
|
|
@@ -10,7 +10,7 @@ const commands = createCommands();
|
|
|
10
10
|
export class JoliboxCloudStorage {
|
|
11
11
|
private gameId: string;
|
|
12
12
|
private httpClient = httpClientManager.create({
|
|
13
|
-
baseUrl:
|
|
13
|
+
baseUrl: getH5ApiHost(context.testMode)
|
|
14
14
|
});
|
|
15
15
|
private gameStore: LocalForage;
|
|
16
16
|
constructor() {
|
package/src/h5/http/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IHttpClient } from '@/common/http';
|
|
2
2
|
import { xUserAgent } from '../../common/http/xua';
|
|
3
3
|
import { context } from '@/common/context';
|
|
4
|
-
import {
|
|
4
|
+
import { getH5ApiHost, platform } from '@jolibox/common';
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
7
|
interface Window {
|
|
@@ -68,7 +68,7 @@ export class JoliboxHttpClient implements IHttpClient {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
constructor(config?: IHttpClientInitParams) {
|
|
71
|
-
const defaultUrl =
|
|
71
|
+
const defaultUrl = getH5ApiHost(context.testMode);
|
|
72
72
|
this.baseUrl = config?.baseUrl ?? defaultUrl;
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { context } from '@/common/context';
|
|
2
2
|
import { EventTracker } from '@/common/report';
|
|
3
3
|
import { httpClientManager } from '@/h5/http';
|
|
4
|
-
import { getCollectHost } from '@jolibox/common';
|
|
4
|
+
import { getCollectHost, getH5ApiHost } from '@jolibox/common';
|
|
5
5
|
|
|
6
6
|
export class H5EventTracker extends EventTracker {
|
|
7
7
|
private get apiBaseURL() {
|
|
@@ -24,4 +24,4 @@ export class H5EventTracker extends EventTracker {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export const tracker = new H5EventTracker();
|
|
27
|
+
export const tracker = new H5EventTracker(getH5ApiHost(context.testMode));
|
package/src/native/api/ads.ts
CHANGED
|
@@ -25,9 +25,12 @@ import {
|
|
|
25
25
|
createJolicoinRewardHandler,
|
|
26
26
|
createJolicoinOnlyRewardHandler,
|
|
27
27
|
createGemRewardHandler,
|
|
28
|
-
createGemOnlyRewardHandler
|
|
28
|
+
createGemOnlyRewardHandler,
|
|
29
|
+
warmupGemBalanceCache,
|
|
30
|
+
warmupBalanceCache
|
|
29
31
|
} from '@/common/rewards';
|
|
30
32
|
import { adEventEmitter } from '@/common/ads';
|
|
33
|
+
import { warmupRewardCache } from '@/common/rewards/fetch-reward';
|
|
31
34
|
|
|
32
35
|
const checkNetworkStatus = () => {
|
|
33
36
|
const { data } = invokeNative('getNetworkStatusSync');
|
|
@@ -168,6 +171,9 @@ const showUnlockSuccessToast = (params: { quantity: number; balance: number }) =
|
|
|
168
171
|
});
|
|
169
172
|
};
|
|
170
173
|
|
|
174
|
+
warmupRewardCache(httpClient);
|
|
175
|
+
warmupGemBalanceCache(httpClient);
|
|
176
|
+
|
|
171
177
|
export const showGemSuccessToast = (params: { quantity: number; balance: number }) => {
|
|
172
178
|
const { quantity } = params;
|
|
173
179
|
const toastTemplate = `{slot-correct} ${quantity * -1} {slot-gem}`;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createCommands } from '@jolibox/common';
|
|
2
2
|
import { createAPI, registerCanIUse, t } from './base';
|
|
3
|
-
import {
|
|
3
|
+
import { createJoliGemPaymentService } from '../payment/payment-service';
|
|
4
4
|
import { createAPIError } from '@/common/report/errors';
|
|
5
5
|
import { IPaymentChoice } from '@jolibox/ui/dist/bridge/coin';
|
|
6
6
|
|
|
7
7
|
const commands = createCommands();
|
|
8
|
-
const paymentService =
|
|
8
|
+
const paymentService = createJoliGemPaymentService();
|
|
9
9
|
|
|
10
10
|
const purchaseGem = createAPI('purchaseGem', {
|
|
11
11
|
paramsSchema: t.tuple(
|
|
@@ -14,7 +14,7 @@ const purchaseGem = createAPI('purchaseGem', {
|
|
|
14
14
|
})
|
|
15
15
|
),
|
|
16
16
|
implement: async (params: { productId: string }): Promise<{ totalAmount: string }> => {
|
|
17
|
-
const result = await paymentService.purchase(
|
|
17
|
+
const result = await paymentService.purchase(params.productId);
|
|
18
18
|
|
|
19
19
|
if (result.code !== 'SUCCESS' || !result.data?.totalAmount) {
|
|
20
20
|
throw createAPIError({
|
|
@@ -32,7 +32,7 @@ const purchaseGem = createAPI('purchaseGem', {
|
|
|
32
32
|
const getGemProducts = createAPI('getGemProducts', {
|
|
33
33
|
paramsSchema: t.tuple(),
|
|
34
34
|
implement: async (): Promise<{ products: IPaymentChoice[] }> => {
|
|
35
|
-
const choices = await paymentService.
|
|
35
|
+
const choices = (await paymentService.getProductsInfo())?.paymentChoices ?? [];
|
|
36
36
|
console.info('choices', choices);
|
|
37
37
|
return {
|
|
38
38
|
products: choices.map((choice) => ({
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// 导入类以便访问静态方法
|
|
2
|
+
import {
|
|
3
|
+
JoliGemPaymentService,
|
|
4
|
+
clearNativePriceCache,
|
|
5
|
+
resetFailureCounters,
|
|
6
|
+
getFailureCount
|
|
7
|
+
} from '../payment-service';
|
|
2
8
|
|
|
3
9
|
// Mock dependencies
|
|
4
10
|
jest.mock('@jolibox/native-bridge', () => ({
|
|
@@ -20,7 +26,8 @@ const mockApplyNative = applyNative;
|
|
|
20
26
|
const mockFetch = fetch;
|
|
21
27
|
|
|
22
28
|
describe('PaymentService - Basic Tests', () => {
|
|
23
|
-
const mockApiEndpoint = '/api/joli-
|
|
29
|
+
const mockApiEndpoint = '/api/joli-gem/balance-detail';
|
|
30
|
+
let paymentService: JoliGemPaymentService;
|
|
24
31
|
|
|
25
32
|
const mockServerData = {
|
|
26
33
|
balance: 100,
|
|
@@ -42,7 +49,9 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
42
49
|
jest.clearAllMocks();
|
|
43
50
|
jest.clearAllTimers();
|
|
44
51
|
jest.useFakeTimers();
|
|
45
|
-
|
|
52
|
+
paymentService = new JoliGemPaymentService(); // 每次创建新实例
|
|
53
|
+
clearNativePriceCache(); // Clear native price cache
|
|
54
|
+
resetFailureCounters(); // Clear failure counters
|
|
46
55
|
});
|
|
47
56
|
|
|
48
57
|
afterEach(() => {
|
|
@@ -63,7 +72,7 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
63
72
|
data: mockNativeData
|
|
64
73
|
});
|
|
65
74
|
|
|
66
|
-
const result = await
|
|
75
|
+
const result = await paymentService.getProductsInfo();
|
|
67
76
|
|
|
68
77
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
69
78
|
expect(mockApplyNative).toHaveBeenCalledTimes(1);
|
|
@@ -80,7 +89,8 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
80
89
|
]
|
|
81
90
|
});
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
// Check cache stats using RequestCacheService methods
|
|
93
|
+
const stats = paymentService.getCacheStats();
|
|
84
94
|
expect(stats.cacheCount).toBe(1);
|
|
85
95
|
expect(stats.validCount).toBe(1);
|
|
86
96
|
});
|
|
@@ -99,17 +109,17 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
99
109
|
});
|
|
100
110
|
|
|
101
111
|
// First call
|
|
102
|
-
await
|
|
112
|
+
await paymentService.getProductsInfo();
|
|
103
113
|
|
|
104
|
-
// Second call - should use cache
|
|
105
|
-
const result = await
|
|
114
|
+
// Second call - should use cache for native prices but still fetch real-time data
|
|
115
|
+
const result = await paymentService.getProductsInfo();
|
|
106
116
|
|
|
107
|
-
expect(mockFetch).toHaveBeenCalledTimes(
|
|
108
|
-
expect(mockApplyNative).toHaveBeenCalledTimes(1); // Only called once
|
|
117
|
+
expect(mockFetch).toHaveBeenCalledTimes(1); // Still fetch for real-time data (balance, enableAutoDeduct)
|
|
118
|
+
expect(mockApplyNative).toHaveBeenCalledTimes(1); // Only called once due to native price caching
|
|
109
119
|
|
|
110
120
|
expect(result).toEqual({
|
|
111
|
-
balance:
|
|
112
|
-
enableAutoDeduct:
|
|
121
|
+
balance: 0,
|
|
122
|
+
enableAutoDeduct: false,
|
|
113
123
|
paymentChoices: [
|
|
114
124
|
{
|
|
115
125
|
productId: 'coin_100',
|
|
@@ -121,16 +131,16 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
121
131
|
});
|
|
122
132
|
});
|
|
123
133
|
|
|
124
|
-
it('should timeout after
|
|
134
|
+
it('should timeout after 1 second', async () => {
|
|
125
135
|
// Mock a slow server response
|
|
126
|
-
mockFetch.mockImplementation(() => new Promise((resolve) => setTimeout(resolve,
|
|
136
|
+
mockFetch.mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 2000)));
|
|
127
137
|
|
|
128
|
-
const promise =
|
|
138
|
+
const promise = paymentService.getProductsInfo();
|
|
129
139
|
|
|
130
|
-
// Fast forward past timeout
|
|
131
|
-
jest.advanceTimersByTime(
|
|
140
|
+
// Fast forward past timeout (1 second)
|
|
141
|
+
jest.advanceTimersByTime(1000);
|
|
132
142
|
|
|
133
|
-
await expect(promise).rejects.toThrow('[
|
|
143
|
+
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 1 seconds');
|
|
134
144
|
});
|
|
135
145
|
|
|
136
146
|
it('should handle cache expiration', async () => {
|
|
@@ -147,13 +157,13 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
147
157
|
});
|
|
148
158
|
|
|
149
159
|
// First call
|
|
150
|
-
await
|
|
160
|
+
await paymentService.getProductsInfo();
|
|
151
161
|
|
|
152
162
|
// Fast forward time beyond cache duration (10 minutes)
|
|
153
|
-
jest.advanceTimersByTime(
|
|
163
|
+
jest.advanceTimersByTime(40 * 60 * 1000);
|
|
154
164
|
|
|
155
165
|
// Second call - cache should be expired
|
|
156
|
-
await
|
|
166
|
+
await paymentService.getProductsInfo();
|
|
157
167
|
|
|
158
168
|
expect(mockApplyNative).toHaveBeenCalledTimes(2); // Called again due to expiration
|
|
159
169
|
});
|
|
@@ -170,22 +180,22 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
170
180
|
// Mock applyNative to reject
|
|
171
181
|
mockApplyNative.mockRejectedValue(new Error('Native call failed'));
|
|
172
182
|
|
|
173
|
-
await expect(
|
|
183
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Native call failed');
|
|
174
184
|
});
|
|
175
185
|
|
|
176
186
|
it('should throw error when mockFetch fails', async () => {
|
|
177
187
|
// Mock fetch to reject
|
|
178
188
|
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
179
189
|
|
|
180
|
-
await expect(
|
|
190
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
181
191
|
});
|
|
182
192
|
|
|
183
|
-
it('should throw error when request exceeds
|
|
184
|
-
// Mock a very slow fetch request that never resolves within
|
|
193
|
+
it('should throw error when request exceeds 1 second timeout', async () => {
|
|
194
|
+
// Mock a very slow fetch request that never resolves within 1 second
|
|
185
195
|
mockFetch.mockImplementation(
|
|
186
196
|
() =>
|
|
187
197
|
new Promise((resolve) => {
|
|
188
|
-
// This will resolve after
|
|
198
|
+
// This will resolve after 2 seconds, but timeout should trigger at 1 second
|
|
189
199
|
setTimeout(() => {
|
|
190
200
|
resolve({
|
|
191
201
|
response: {
|
|
@@ -194,15 +204,71 @@ describe('PaymentService - Basic Tests', () => {
|
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
});
|
|
197
|
-
},
|
|
207
|
+
}, 2000);
|
|
198
208
|
})
|
|
199
209
|
);
|
|
200
210
|
|
|
201
|
-
const promise =
|
|
211
|
+
const promise = paymentService.getProductsInfo();
|
|
212
|
+
|
|
213
|
+
// Advance timers to 1 second to trigger timeout
|
|
214
|
+
jest.advanceTimersByTime(1000);
|
|
215
|
+
|
|
216
|
+
await expect(promise).rejects.toThrow('[RequestCacheService] Request timeout after 1 seconds');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should increment failure count on failed requests', async () => {
|
|
220
|
+
// Mock fetch to reject
|
|
221
|
+
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
222
|
+
|
|
223
|
+
// First failure
|
|
224
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
225
|
+
expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(1);
|
|
226
|
+
|
|
227
|
+
// Second failure
|
|
228
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
229
|
+
expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(2);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should block requests after 2 failures', async () => {
|
|
233
|
+
// Mock fetch to reject
|
|
234
|
+
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
235
|
+
|
|
236
|
+
// First two failures
|
|
237
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
238
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
239
|
+
|
|
240
|
+
// Third call should be blocked immediately (failure count is now 2, which >= MAX_FAILURE_COUNT)
|
|
241
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow(
|
|
242
|
+
'getProductsInfo has failed more than 2 times for /api/joli-gem/balance-detail'
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Verify fetch was only called twice (not three times)
|
|
246
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should reset failure count on successful request', async () => {
|
|
250
|
+
// Mock first call to fail
|
|
251
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'));
|
|
252
|
+
|
|
253
|
+
// First failure
|
|
254
|
+
await expect(paymentService.getProductsInfo()).rejects.toThrow('Network error');
|
|
255
|
+
expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(1);
|
|
256
|
+
|
|
257
|
+
// Mock second call to succeed
|
|
258
|
+
mockFetch.mockResolvedValue({
|
|
259
|
+
response: {
|
|
260
|
+
data: {
|
|
261
|
+
data: mockServerData
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
202
265
|
|
|
203
|
-
|
|
204
|
-
|
|
266
|
+
mockApplyNative.mockResolvedValue({
|
|
267
|
+
data: mockNativeData
|
|
268
|
+
});
|
|
205
269
|
|
|
206
|
-
|
|
270
|
+
// Successful call should reset counter
|
|
271
|
+
await paymentService.getProductsInfo();
|
|
272
|
+
expect(getFailureCount('/api/joli-gem/balance-detail')).toBe(0);
|
|
207
273
|
});
|
|
208
274
|
});
|