@karn_lat/protocol-sdk 0.1.0-alpha.1
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/README.md +68 -0
- package/dist/__tests__/setup.d.ts +14 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +44 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/clients/GovernorClient.d.ts +9 -0
- package/dist/clients/GovernorClient.d.ts.map +1 -0
- package/dist/clients/GovernorClient.js +18 -0
- package/dist/clients/GovernorClient.js.map +1 -0
- package/dist/clients/TreasuryClient.d.ts +9 -0
- package/dist/clients/TreasuryClient.d.ts.map +1 -0
- package/dist/clients/TreasuryClient.js +18 -0
- package/dist/clients/TreasuryClient.js.map +1 -0
- package/dist/clients/ValocracyClient.d.ts +13 -0
- package/dist/clients/ValocracyClient.d.ts.map +1 -0
- package/dist/clients/ValocracyClient.js +32 -0
- package/dist/clients/ValocracyClient.js.map +1 -0
- package/dist/clients/index.d.ts +4 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +4 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/generated/governor/src/index.d.ts +400 -0
- package/dist/generated/governor/src/index.d.ts.map +1 -0
- package/dist/generated/governor/src/index.js +63 -0
- package/dist/generated/governor/src/index.js.map +1 -0
- package/dist/generated/treasury/src/index.d.ts +474 -0
- package/dist/generated/treasury/src/index.d.ts.map +1 -0
- package/dist/generated/treasury/src/index.js +54 -0
- package/dist/generated/treasury/src/index.js.map +1 -0
- package/dist/generated/valocracy/src/index.d.ts +807 -0
- package/dist/generated/valocracy/src/index.d.ts.map +1 -0
- package/dist/generated/valocracy/src/index.js +114 -0
- package/dist/generated/valocracy/src/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/react/hooks/useGovernor.d.ts +24 -0
- package/dist/react/hooks/useGovernor.d.ts.map +1 -0
- package/dist/react/hooks/useGovernor.js +45 -0
- package/dist/react/hooks/useGovernor.js.map +1 -0
- package/dist/react/hooks/useMultiWallet.d.ts +35 -0
- package/dist/react/hooks/useMultiWallet.d.ts.map +1 -0
- package/dist/react/hooks/useMultiWallet.js +87 -0
- package/dist/react/hooks/useMultiWallet.js.map +1 -0
- package/dist/react/hooks/useTreasury.d.ts +14 -0
- package/dist/react/hooks/useTreasury.d.ts.map +1 -0
- package/dist/react/hooks/useTreasury.js +45 -0
- package/dist/react/hooks/useTreasury.js.map +1 -0
- package/dist/react/hooks/useValocracy.d.ts +16 -0
- package/dist/react/hooks/useValocracy.d.ts.map +1 -0
- package/dist/react/hooks/useValocracy.js +54 -0
- package/dist/react/hooks/useValocracy.js.map +1 -0
- package/dist/react/hooks/useWallet.d.ts +13 -0
- package/dist/react/hooks/useWallet.d.ts.map +1 -0
- package/dist/react/hooks/useWallet.js +51 -0
- package/dist/react/hooks/useWallet.js.map +1 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +7 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/providers/KarnProvider.d.ts +25 -0
- package/dist/react/providers/KarnProvider.d.ts.map +1 -0
- package/dist/react/providers/KarnProvider.js +25 -0
- package/dist/react/providers/KarnProvider.js.map +1 -0
- package/dist/utils/decay.d.ts +19 -0
- package/dist/utils/decay.d.ts.map +1 -0
- package/dist/utils/decay.js +33 -0
- package/dist/utils/decay.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/polling.d.ts +75 -0
- package/dist/utils/polling.d.ts.map +1 -0
- package/dist/utils/polling.js +104 -0
- package/dist/utils/polling.js.map +1 -0
- package/dist/utils/simulation.d.ts +67 -0
- package/dist/utils/simulation.d.ts.map +1 -0
- package/dist/utils/simulation.js +88 -0
- package/dist/utils/simulation.js.map +1 -0
- package/dist/wallet/WalletManager.d.ts +77 -0
- package/dist/wallet/WalletManager.d.ts.map +1 -0
- package/dist/wallet/WalletManager.js +268 -0
- package/dist/wallet/WalletManager.js.map +1 -0
- package/dist/wallet/adapters/AlbedoAdapter.d.ts +47 -0
- package/dist/wallet/adapters/AlbedoAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/AlbedoAdapter.js +84 -0
- package/dist/wallet/adapters/AlbedoAdapter.js.map +1 -0
- package/dist/wallet/adapters/FreighterAdapter.d.ts +42 -0
- package/dist/wallet/adapters/FreighterAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/FreighterAdapter.js +107 -0
- package/dist/wallet/adapters/FreighterAdapter.js.map +1 -0
- package/dist/wallet/adapters/LobstrAdapter.d.ts +34 -0
- package/dist/wallet/adapters/LobstrAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/LobstrAdapter.js +89 -0
- package/dist/wallet/adapters/LobstrAdapter.js.map +1 -0
- package/dist/wallet/adapters/RabetAdapter.d.ts +39 -0
- package/dist/wallet/adapters/RabetAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/RabetAdapter.js +104 -0
- package/dist/wallet/adapters/RabetAdapter.js.map +1 -0
- package/dist/wallet/adapters/xBullAdapter.d.ts +41 -0
- package/dist/wallet/adapters/xBullAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/xBullAdapter.js +72 -0
- package/dist/wallet/adapters/xBullAdapter.js.map +1 -0
- package/dist/wallet/index.d.ts +20 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +23 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/types.d.ts +165 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +50 -0
- package/dist/wallet/types.js.map +1 -0
- package/examples/basic-usage.ts +28 -0
- package/jest.config.js +37 -0
- package/package.json +58 -0
- package/src/__tests__/README.md +364 -0
- package/src/__tests__/setup.ts +57 -0
- package/src/__tests__/utils/decay.test.ts +331 -0
- package/src/__tests__/wallet/WalletManager.test.ts +410 -0
- package/src/clients/GovernorClient.ts +23 -0
- package/src/clients/TreasuryClient.ts +23 -0
- package/src/clients/ValocracyClient.ts +48 -0
- package/src/clients/index.ts +3 -0
- package/src/generated/governor/README.md +54 -0
- package/src/generated/governor/package.json +17 -0
- package/src/generated/governor/src/index.ts +428 -0
- package/src/generated/governor/tsconfig.json +98 -0
- package/src/generated/treasury/README.md +54 -0
- package/src/generated/treasury/package.json +17 -0
- package/src/generated/treasury/src/index.ts +495 -0
- package/src/generated/treasury/tsconfig.json +98 -0
- package/src/generated/valocracy/README.md +54 -0
- package/src/generated/valocracy/package.json +17 -0
- package/src/generated/valocracy/src/index.ts +831 -0
- package/src/generated/valocracy/tsconfig.json +98 -0
- package/src/index.ts +4 -0
- package/src/react/hooks/useGovernor.ts +69 -0
- package/src/react/hooks/useMultiWallet.ts +169 -0
- package/src/react/hooks/useTreasury.ts +57 -0
- package/src/react/hooks/useValocracy.ts +66 -0
- package/src/react/hooks/useWallet.ts +60 -0
- package/src/react/index.ts +6 -0
- package/src/react/providers/KarnProvider.tsx +63 -0
- package/src/utils/decay.ts +44 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/polling.ts +193 -0
- package/src/utils/simulation.ts +136 -0
- package/src/wallet/WalletManager.ts +360 -0
- package/src/wallet/adapters/AlbedoAdapter.ts +140 -0
- package/src/wallet/adapters/FreighterAdapter.ts +179 -0
- package/src/wallet/adapters/LobstrAdapter.ts +142 -0
- package/src/wallet/adapters/RabetAdapter.ts +162 -0
- package/src/wallet/adapters/xBullAdapter.ts +123 -0
- package/src/wallet/index.ts +37 -0
- package/src/wallet/types.ts +204 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Mana decay calculation utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { calculateMana } from '../../utils/decay.js';
|
|
6
|
+
|
|
7
|
+
describe('calculateMana', () => {
|
|
8
|
+
const MEMBER_FLOOR = 5;
|
|
9
|
+
const VACANCY_PERIOD = 15552000; // 180 days in seconds
|
|
10
|
+
const ONE_DAY = 24 * 60 * 60;
|
|
11
|
+
|
|
12
|
+
describe('Basic Calculations', () => {
|
|
13
|
+
it('should return Member Floor for newly registered member with no badges', () => {
|
|
14
|
+
const level = 0;
|
|
15
|
+
const permanentLevel = 0;
|
|
16
|
+
const expiry = Date.now() / 1000 + VACANCY_PERIOD;
|
|
17
|
+
const currentTimestamp = Date.now() / 1000;
|
|
18
|
+
|
|
19
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
20
|
+
|
|
21
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should calculate correct Mana with full time remaining', () => {
|
|
25
|
+
const level = 100;
|
|
26
|
+
const permanentLevel = 0;
|
|
27
|
+
const currentTimestamp = Date.now() / 1000;
|
|
28
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
29
|
+
|
|
30
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
31
|
+
|
|
32
|
+
// Mana = 5 (floor) + 100 (full level with full time) + 0 (permanent) = 105
|
|
33
|
+
expect(mana).toBe(105);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should calculate correct Mana with partial time remaining (50%)', () => {
|
|
37
|
+
const level = 100;
|
|
38
|
+
const permanentLevel = 0;
|
|
39
|
+
const currentTimestamp = Date.now() / 1000;
|
|
40
|
+
const expiry = currentTimestamp + (VACANCY_PERIOD / 2); // 90 days remaining
|
|
41
|
+
|
|
42
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
43
|
+
|
|
44
|
+
// bonus = floor(100 * (VACANCY_PERIOD/2) / VACANCY_PERIOD) = 50
|
|
45
|
+
// Mana = 5 + 50 + 0 = 55
|
|
46
|
+
expect(mana).toBe(55);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return Member Floor when fully decayed (time expired)', () => {
|
|
50
|
+
const level = 100;
|
|
51
|
+
const permanentLevel = 0;
|
|
52
|
+
const currentTimestamp = Date.now() / 1000;
|
|
53
|
+
const expiry = currentTimestamp - ONE_DAY; // Expired 1 day ago
|
|
54
|
+
|
|
55
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
56
|
+
|
|
57
|
+
// bonus = 0 (time expired)
|
|
58
|
+
// Mana = 5 + 0 + 0 = 5
|
|
59
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Permanent Level (Founder Badge)', () => {
|
|
64
|
+
it('should include permanent level in Mana regardless of time', () => {
|
|
65
|
+
const level = 100;
|
|
66
|
+
const permanentLevel = 100;
|
|
67
|
+
const currentTimestamp = Date.now() / 1000;
|
|
68
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
69
|
+
|
|
70
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
71
|
+
|
|
72
|
+
// decayingLevel = 100 - 100 = 0
|
|
73
|
+
// bonus = 0
|
|
74
|
+
// Mana = 5 + 0 + 100 = 105
|
|
75
|
+
expect(mana).toBe(105);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should maintain permanent Mana even after expiry', () => {
|
|
79
|
+
const level = 100;
|
|
80
|
+
const permanentLevel = 100;
|
|
81
|
+
const currentTimestamp = Date.now() / 1000;
|
|
82
|
+
const expiry = currentTimestamp - 365 * ONE_DAY; // Expired 1 year ago
|
|
83
|
+
|
|
84
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
85
|
+
|
|
86
|
+
// Permanent level never decays
|
|
87
|
+
// Mana = 5 + 0 + 100 = 105
|
|
88
|
+
expect(mana).toBe(105);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle mixed permanent and decaying levels', () => {
|
|
92
|
+
const level = 150; // 50 permanent + 100 decaying
|
|
93
|
+
const permanentLevel = 50;
|
|
94
|
+
const currentTimestamp = Date.now() / 1000;
|
|
95
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
96
|
+
|
|
97
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
98
|
+
|
|
99
|
+
// decayingLevel = 150 - 50 = 100
|
|
100
|
+
// bonus = 100 (full time)
|
|
101
|
+
// Mana = 5 + 100 + 50 = 155
|
|
102
|
+
expect(mana).toBe(155);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should calculate correct decay with mixed levels at 50% time', () => {
|
|
106
|
+
const level = 150;
|
|
107
|
+
const permanentLevel = 50;
|
|
108
|
+
const currentTimestamp = Date.now() / 1000;
|
|
109
|
+
const expiry = currentTimestamp + (VACANCY_PERIOD / 2);
|
|
110
|
+
|
|
111
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
112
|
+
|
|
113
|
+
// decayingLevel = 100
|
|
114
|
+
// bonus = floor(100 * 0.5) = 50
|
|
115
|
+
// Mana = 5 + 50 + 50 = 105
|
|
116
|
+
expect(mana).toBe(105);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Edge Cases', () => {
|
|
121
|
+
it('should handle zero level', () => {
|
|
122
|
+
const level = 0;
|
|
123
|
+
const permanentLevel = 0;
|
|
124
|
+
const currentTimestamp = Date.now() / 1000;
|
|
125
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
126
|
+
|
|
127
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
128
|
+
|
|
129
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle very large level values', () => {
|
|
133
|
+
const level = 1000000;
|
|
134
|
+
const permanentLevel = 0;
|
|
135
|
+
const currentTimestamp = Date.now() / 1000;
|
|
136
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
137
|
+
|
|
138
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
139
|
+
|
|
140
|
+
expect(mana).toBe(MEMBER_FLOOR + 1000000);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle expiry exactly equal to current time', () => {
|
|
144
|
+
const level = 100;
|
|
145
|
+
const permanentLevel = 0;
|
|
146
|
+
const currentTimestamp = Date.now() / 1000;
|
|
147
|
+
const expiry = currentTimestamp;
|
|
148
|
+
|
|
149
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
150
|
+
|
|
151
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle very far future expiry', () => {
|
|
155
|
+
const level = 100;
|
|
156
|
+
const permanentLevel = 0;
|
|
157
|
+
const currentTimestamp = Date.now() / 1000;
|
|
158
|
+
const expiry = currentTimestamp + (10 * VACANCY_PERIOD); // 10x vacancy period
|
|
159
|
+
|
|
160
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
161
|
+
|
|
162
|
+
// bonus = floor(100 * (10 * VACANCY_PERIOD) / VACANCY_PERIOD) = 1000
|
|
163
|
+
// Mana = 5 + 1000 + 0 = 1005
|
|
164
|
+
expect(mana).toBe(1005);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should use floor function for fractional values', () => {
|
|
168
|
+
const level = 3;
|
|
169
|
+
const permanentLevel = 0;
|
|
170
|
+
const currentTimestamp = 0;
|
|
171
|
+
const expiry = VACANCY_PERIOD / 2; // 50% time
|
|
172
|
+
|
|
173
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
174
|
+
|
|
175
|
+
// bonus = floor(3 * 0.5) = floor(1.5) = 1
|
|
176
|
+
// Mana = 5 + 1 + 0 = 6
|
|
177
|
+
expect(mana).toBe(6);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Time Progression Scenarios', () => {
|
|
182
|
+
it('should decrease Mana linearly over time', () => {
|
|
183
|
+
const level = 100;
|
|
184
|
+
const permanentLevel = 0;
|
|
185
|
+
const startTime = 0;
|
|
186
|
+
const expiry = VACANCY_PERIOD;
|
|
187
|
+
|
|
188
|
+
// At start (100% time remaining)
|
|
189
|
+
const manaAt0 = calculateMana(level, permanentLevel, expiry, startTime);
|
|
190
|
+
expect(manaAt0).toBe(105);
|
|
191
|
+
|
|
192
|
+
// At 25% time remaining
|
|
193
|
+
const time25 = expiry - (VACANCY_PERIOD * 0.25);
|
|
194
|
+
const manaAt25 = calculateMana(level, permanentLevel, expiry, time25);
|
|
195
|
+
expect(manaAt25).toBe(30); // 5 + 25 + 0
|
|
196
|
+
|
|
197
|
+
// At 50% time remaining
|
|
198
|
+
const time50 = expiry - (VACANCY_PERIOD * 0.5);
|
|
199
|
+
const manaAt50 = calculateMana(level, permanentLevel, expiry, time50);
|
|
200
|
+
expect(manaAt50).toBe(55); // 5 + 50 + 0
|
|
201
|
+
|
|
202
|
+
// At 75% time remaining
|
|
203
|
+
const time75 = expiry - (VACANCY_PERIOD * 0.75);
|
|
204
|
+
const manaAt75 = calculateMana(level, permanentLevel, expiry, time75);
|
|
205
|
+
expect(manaAt75).toBe(80); // 5 + 75 + 0
|
|
206
|
+
|
|
207
|
+
// After expiry
|
|
208
|
+
const timeExpired = expiry + ONE_DAY;
|
|
209
|
+
const manaExpired = calculateMana(level, permanentLevel, expiry, timeExpired);
|
|
210
|
+
expect(manaExpired).toBe(5); // Member Floor only
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should handle decay over 90 days correctly', () => {
|
|
214
|
+
const level = 100;
|
|
215
|
+
const permanentLevel = 0;
|
|
216
|
+
const startTime = 1000000;
|
|
217
|
+
const expiry = startTime + VACANCY_PERIOD;
|
|
218
|
+
|
|
219
|
+
// After 90 days (half vacancy period)
|
|
220
|
+
const after90Days = startTime + (90 * ONE_DAY);
|
|
221
|
+
const timeRemaining = expiry - after90Days;
|
|
222
|
+
const expectedBonus = Math.floor((level * timeRemaining) / VACANCY_PERIOD);
|
|
223
|
+
|
|
224
|
+
const mana = calculateMana(level, permanentLevel, expiry, after90Days);
|
|
225
|
+
|
|
226
|
+
expect(mana).toBe(MEMBER_FLOOR + expectedBonus);
|
|
227
|
+
// Should be approximately 55 (5 + 50)
|
|
228
|
+
expect(mana).toBeWithinRange(50, 60);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should handle decay over 180 days correctly', () => {
|
|
232
|
+
const level = 100;
|
|
233
|
+
const permanentLevel = 0;
|
|
234
|
+
const startTime = 1000000;
|
|
235
|
+
const expiry = startTime + VACANCY_PERIOD;
|
|
236
|
+
|
|
237
|
+
// After exactly 180 days (full vacancy period)
|
|
238
|
+
const after180Days = startTime + VACANCY_PERIOD;
|
|
239
|
+
|
|
240
|
+
const mana = calculateMana(level, permanentLevel, expiry, after180Days);
|
|
241
|
+
|
|
242
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('Real-world Scenarios', () => {
|
|
247
|
+
it('should calculate Mana for member with Learning Path badge (level 20) after 30 days', () => {
|
|
248
|
+
const level = 20;
|
|
249
|
+
const permanentLevel = 0;
|
|
250
|
+
const currentTimestamp = Date.now() / 1000;
|
|
251
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
252
|
+
const thirtyDaysLater = currentTimestamp + (30 * ONE_DAY);
|
|
253
|
+
|
|
254
|
+
const mana = calculateMana(level, permanentLevel, expiry, thirtyDaysLater);
|
|
255
|
+
|
|
256
|
+
// Time remaining = 150 days out of 180
|
|
257
|
+
// bonus = floor(20 * (150 * ONE_DAY) / VACANCY_PERIOD)
|
|
258
|
+
// ≈ floor(20 * 0.833) ≈ 16
|
|
259
|
+
// Mana ≈ 5 + 16 = 21
|
|
260
|
+
expect(mana).toBeWithinRange(18, 23);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should calculate Mana for Founder (permanent level 100)', () => {
|
|
264
|
+
const level = 100;
|
|
265
|
+
const permanentLevel = 100;
|
|
266
|
+
const currentTimestamp = Date.now() / 1000;
|
|
267
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
268
|
+
const oneYearLater = currentTimestamp + (365 * ONE_DAY);
|
|
269
|
+
|
|
270
|
+
const mana = calculateMana(level, permanentLevel, expiry, oneYearLater);
|
|
271
|
+
|
|
272
|
+
// Founder Mana never decays
|
|
273
|
+
expect(mana).toBe(105);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should calculate Mana for member with multiple badges (level 150) after 60 days', () => {
|
|
277
|
+
const level = 150; // e.g., 3 badges x 50 level each
|
|
278
|
+
const permanentLevel = 0;
|
|
279
|
+
const currentTimestamp = Date.now() / 1000;
|
|
280
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
281
|
+
const sixtyDaysLater = currentTimestamp + (60 * ONE_DAY);
|
|
282
|
+
|
|
283
|
+
const mana = calculateMana(level, permanentLevel, expiry, sixtyDaysLater);
|
|
284
|
+
|
|
285
|
+
// Time remaining = 120 days out of 180 = 66.67%
|
|
286
|
+
// bonus = floor(150 * 0.6667) = floor(100) = 100
|
|
287
|
+
// Mana = 5 + 100 = 105
|
|
288
|
+
expect(mana).toBeWithinRange(95, 110);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('Boundary Conditions', () => {
|
|
293
|
+
it('should not return negative Mana', () => {
|
|
294
|
+
const level = 100;
|
|
295
|
+
const permanentLevel = 0;
|
|
296
|
+
const currentTimestamp = Date.now() / 1000;
|
|
297
|
+
const expiry = currentTimestamp - (1000 * ONE_DAY); // Expired long ago
|
|
298
|
+
|
|
299
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
300
|
+
|
|
301
|
+
expect(mana).toBeGreaterThanOrEqual(0);
|
|
302
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should handle negative time remaining gracefully', () => {
|
|
306
|
+
const level = 100;
|
|
307
|
+
const permanentLevel = 0;
|
|
308
|
+
const currentTimestamp = Date.now() / 1000;
|
|
309
|
+
const expiry = currentTimestamp - 1000;
|
|
310
|
+
|
|
311
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
312
|
+
|
|
313
|
+
expect(mana).toBe(MEMBER_FLOOR);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle level less than permanent level', () => {
|
|
317
|
+
// This shouldn't happen in practice, but test defensive code
|
|
318
|
+
const level = 50;
|
|
319
|
+
const permanentLevel = 100;
|
|
320
|
+
const currentTimestamp = Date.now() / 1000;
|
|
321
|
+
const expiry = currentTimestamp + VACANCY_PERIOD;
|
|
322
|
+
|
|
323
|
+
const mana = calculateMana(level, permanentLevel, expiry, currentTimestamp);
|
|
324
|
+
|
|
325
|
+
// decayingLevel = max(0, 50 - 100) = 0
|
|
326
|
+
// bonus = 0
|
|
327
|
+
// Mana = 5 + 0 + 100 = 105
|
|
328
|
+
expect(mana).toBe(105);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
});
|