@framers/agentos-ext-wallet 0.2.0 → 0.2.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.
@@ -1,153 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Unit tests for MccCategoryMap.
4
- *
5
- * Tests cover: category resolution for all known ranges, boundary values,
6
- * unknown MCCs, reverse lookup (category → ranges/codes), and mapped categories.
7
- */
8
-
9
- import { describe, it, expect } from 'vitest';
10
- import { MccCategoryMap } from '../src/cards/MccCategoryMap.js';
11
-
12
- describe('MccCategoryMap', () => {
13
- const map = new MccCategoryMap();
14
-
15
- /* ── mccToCategory ────────────────────────────────────────────────── */
16
-
17
- describe('mccToCategory', () => {
18
- it('should resolve dining MCCs (5812-5814)', () => {
19
- expect(map.mccToCategory('5812')).toBe('dining');
20
- expect(map.mccToCategory('5813')).toBe('dining');
21
- expect(map.mccToCategory('5814')).toBe('dining');
22
- });
23
-
24
- it('should resolve travel MCCs (airlines, hotels)', () => {
25
- expect(map.mccToCategory('3000')).toBe('travel');
26
- expect(map.mccToCategory('3500')).toBe('travel');
27
- expect(map.mccToCategory('3999')).toBe('travel');
28
- expect(map.mccToCategory('4511')).toBe('travel');
29
- expect(map.mccToCategory('7011')).toBe('travel');
30
- expect(map.mccToCategory('7012')).toBe('travel');
31
- });
32
-
33
- it('should resolve shopping MCCs (retail)', () => {
34
- expect(map.mccToCategory('5200')).toBe('shopping');
35
- expect(map.mccToCategory('5411')).toBe('shopping');
36
- expect(map.mccToCategory('5699')).toBe('shopping');
37
- expect(map.mccToCategory('5999')).toBe('shopping');
38
- });
39
-
40
- it('should resolve entertainment MCCs', () => {
41
- expect(map.mccToCategory('7832')).toBe('entertainment');
42
- expect(map.mccToCategory('7841')).toBe('entertainment');
43
- expect(map.mccToCategory('7941')).toBe('entertainment');
44
- });
45
-
46
- it('should resolve utilities MCCs (telecom, electric)', () => {
47
- expect(map.mccToCategory('4812')).toBe('utilities');
48
- expect(map.mccToCategory('4900')).toBe('utilities');
49
- });
50
-
51
- it('should resolve subscriptions MCCs (digital goods)', () => {
52
- expect(map.mccToCategory('5815')).toBe('subscriptions');
53
- expect(map.mccToCategory('5816')).toBe('subscriptions');
54
- expect(map.mccToCategory('5818')).toBe('subscriptions');
55
- });
56
-
57
- it('should resolve web_services MCCs (computer services)', () => {
58
- expect(map.mccToCategory('7372')).toBe('web_services');
59
- expect(map.mccToCategory('7379')).toBe('web_services');
60
- });
61
-
62
- it('should return "other" for unknown MCC', () => {
63
- expect(map.mccToCategory('0000')).toBe('other');
64
- expect(map.mccToCategory('9999')).toBe('other');
65
- expect(map.mccToCategory('1234')).toBe('other');
66
- });
67
-
68
- it('should return "other" for invalid MCC string', () => {
69
- expect(map.mccToCategory('abcd')).toBe('other');
70
- expect(map.mccToCategory('')).toBe('other');
71
- });
72
-
73
- it('should handle boundary values correctly', () => {
74
- // Just before travel range
75
- expect(map.mccToCategory('2999')).toBe('other');
76
- // First travel MCC
77
- expect(map.mccToCategory('3000')).toBe('travel');
78
- // Last travel MCC in range
79
- expect(map.mccToCategory('3999')).toBe('travel');
80
- // Just after travel range
81
- expect(map.mccToCategory('4000')).toBe('other');
82
- });
83
- });
84
-
85
- /* ── categoryToMccRanges ──────────────────────────────────────────── */
86
-
87
- describe('categoryToMccRanges', () => {
88
- it('should return ranges for dining', () => {
89
- const ranges = map.categoryToMccRanges('dining');
90
- expect(ranges.length).toBeGreaterThan(0);
91
- expect(ranges.some(r => r.min === 5812)).toBe(true);
92
- });
93
-
94
- it('should return multiple ranges for travel', () => {
95
- const ranges = map.categoryToMccRanges('travel');
96
- expect(ranges.length).toBeGreaterThan(1);
97
- });
98
-
99
- it('should return empty array for unmapped categories', () => {
100
- const ranges = map.categoryToMccRanges('defi');
101
- expect(ranges).toEqual([]);
102
- });
103
-
104
- it('should return empty array for api_costs (no MCC mapping)', () => {
105
- const ranges = map.categoryToMccRanges('api_costs');
106
- expect(ranges).toEqual([]);
107
- });
108
- });
109
-
110
- /* ── categoryToMccCodes ───────────────────────────────────────────── */
111
-
112
- describe('categoryToMccCodes', () => {
113
- it('should expand dining to individual codes', () => {
114
- const codes = map.categoryToMccCodes('dining');
115
- expect(codes).toContain('5812');
116
- expect(codes).toContain('5813');
117
- expect(codes).toContain('5814');
118
- });
119
-
120
- it('should pad codes to 4 digits', () => {
121
- const codes = map.categoryToMccCodes('dining');
122
- for (const code of codes) {
123
- expect(code.length).toBe(4);
124
- }
125
- });
126
-
127
- it('should return empty for unmapped categories', () => {
128
- expect(map.categoryToMccCodes('transfers')).toEqual([]);
129
- });
130
- });
131
-
132
- /* ── getMappedCategories ──────────────────────────────────────────── */
133
-
134
- describe('getMappedCategories', () => {
135
- it('should return all categories with MCC mappings', () => {
136
- const categories = map.getMappedCategories();
137
- expect(categories).toContain('dining');
138
- expect(categories).toContain('travel');
139
- expect(categories).toContain('shopping');
140
- expect(categories).toContain('entertainment');
141
- expect(categories).toContain('utilities');
142
- expect(categories).toContain('subscriptions');
143
- expect(categories).toContain('web_services');
144
- });
145
-
146
- it('should not include categories without MCC mappings', () => {
147
- const categories = map.getMappedCategories();
148
- expect(categories).not.toContain('defi');
149
- expect(categories).not.toContain('transfers');
150
- expect(categories).not.toContain('api_costs');
151
- });
152
- });
153
- });
@@ -1,254 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Unit tests for SpendingPolicyEnforcer.
4
- *
5
- * Tests cover: per-tx limits, daily/monthly caps, category budgets,
6
- * blocked categories, address whitelists/blacklists, approval thresholds,
7
- * and spend recording.
8
- */
9
-
10
- import { describe, it, expect, beforeEach } from 'vitest';
11
- import { SpendingPolicyEnforcer, type ISpendingLedgerStore } from '../src/SpendingPolicyEnforcer.js';
12
- import type { SpendingPolicyConfig } from '../src/types.js';
13
-
14
- /* ------------------------------------------------------------------ */
15
- /* In-memory ledger for tests */
16
- /* ------------------------------------------------------------------ */
17
-
18
- function createTestLedger(): ISpendingLedgerStore & { entries: any[] } {
19
- const entries: any[] = [];
20
- return {
21
- entries,
22
- async sumByPeriod(agentId: string, periodKey: string) {
23
- return entries
24
- .filter(e => e.agentId === agentId && e.periodKey === periodKey)
25
- .reduce((sum, e) => sum + e.amountUsd, 0);
26
- },
27
- async sumByCategoryAndPeriod(agentId: string, category: string, periodKey: string) {
28
- return entries
29
- .filter(e => e.agentId === agentId && e.category === category && e.periodKey === periodKey)
30
- .reduce((sum, e) => sum + e.amountUsd, 0);
31
- },
32
- async insert(entry: any) {
33
- entries.push(entry);
34
- },
35
- };
36
- }
37
-
38
- const AGENT = 'test-agent-1';
39
-
40
- /* ------------------------------------------------------------------ */
41
- /* Tests */
42
- /* ------------------------------------------------------------------ */
43
-
44
- describe('SpendingPolicyEnforcer', () => {
45
- let policy: SpendingPolicyConfig;
46
- let ledger: ReturnType<typeof createTestLedger>;
47
- let enforcer: SpendingPolicyEnforcer;
48
-
49
- beforeEach(() => {
50
- policy = {
51
- dailyLimitUsd: 50,
52
- perTransactionLimitUsd: 20,
53
- monthlyLimitUsd: 500,
54
- categoryBudgets: [
55
- { category: 'api_costs', dailyLimitUsd: 10, monthlyLimitUsd: 100 },
56
- ],
57
- requireApprovalAboveUsd: 15,
58
- blockedCategories: ['defi'],
59
- allowedAddresses: ['0xAllowed1', '0xAllowed2'],
60
- blockedAddresses: ['0xBlocked1'],
61
- };
62
- ledger = createTestLedger();
63
- enforcer = new SpendingPolicyEnforcer(policy, ledger);
64
- });
65
-
66
- /* ── Per-transaction limit ─────────────────────────────────────── */
67
-
68
- describe('per-transaction limit', () => {
69
- it('should allow transactions within per-tx limit', async () => {
70
- const result = await enforcer.canSpend(AGENT, 15, 'transfers');
71
- expect(result.allowed).toBe(true);
72
- });
73
-
74
- it('should block transactions exceeding per-tx limit', async () => {
75
- const result = await enforcer.canSpend(AGENT, 25, 'transfers');
76
- expect(result.allowed).toBe(false);
77
- expect(result.reason).toContain('per-transaction limit');
78
- });
79
-
80
- it('should block transaction exactly at limit + epsilon', async () => {
81
- const result = await enforcer.canSpend(AGENT, 20.01, 'transfers');
82
- expect(result.allowed).toBe(false);
83
- });
84
- });
85
-
86
- /* ── Daily limit ───────────────────────────────────────────────── */
87
-
88
- describe('daily limit', () => {
89
- it('should allow first transaction within daily limit', async () => {
90
- const result = await enforcer.canSpend(AGENT, 10, 'transfers');
91
- expect(result.allowed).toBe(true);
92
- expect(result.remainingDailyUsd).toBe(40);
93
- });
94
-
95
- it('should block when daily limit would be exceeded', async () => {
96
- // Pre-seed ledger with $45 of spending today
97
- const today = new Date().toISOString().slice(0, 10);
98
- ledger.entries.push({ agentId: AGENT, category: 'transfers', amountUsd: 45, periodKey: today });
99
-
100
- const result = await enforcer.canSpend(AGENT, 10, 'transfers');
101
- expect(result.allowed).toBe(false);
102
- expect(result.reason).toContain('Daily limit');
103
- });
104
-
105
- it('should allow up to remaining daily budget', async () => {
106
- const today = new Date().toISOString().slice(0, 10);
107
- ledger.entries.push({ agentId: AGENT, category: 'transfers', amountUsd: 40, periodKey: today });
108
-
109
- const result = await enforcer.canSpend(AGENT, 10, 'transfers');
110
- expect(result.allowed).toBe(true);
111
- expect(result.remainingDailyUsd).toBe(0);
112
- });
113
- });
114
-
115
- /* ── Monthly limit ─────────────────────────────────────────────── */
116
-
117
- describe('monthly limit', () => {
118
- it('should block when monthly limit would be exceeded', async () => {
119
- const month = new Date().toISOString().slice(0, 7);
120
- ledger.entries.push({ agentId: AGENT, category: 'transfers', amountUsd: 495, periodKey: month });
121
-
122
- const result = await enforcer.canSpend(AGENT, 10, 'transfers');
123
- expect(result.allowed).toBe(false);
124
- expect(result.reason).toContain('Monthly limit');
125
- });
126
- });
127
-
128
- /* ── Category budgets ──────────────────────────────────────────── */
129
-
130
- describe('category budgets', () => {
131
- it('should enforce daily category budget', async () => {
132
- const today = new Date().toISOString().slice(0, 10);
133
- ledger.entries.push({ agentId: AGENT, category: 'api_costs', amountUsd: 8, periodKey: today });
134
-
135
- const result = await enforcer.canSpend(AGENT, 5, 'api_costs');
136
- expect(result.allowed).toBe(false);
137
- expect(result.reason).toContain('"api_costs" budget');
138
- });
139
-
140
- it('should enforce monthly category budget', async () => {
141
- const month = new Date().toISOString().slice(0, 7);
142
- ledger.entries.push({ agentId: AGENT, category: 'api_costs', amountUsd: 98, periodKey: month });
143
-
144
- const result = await enforcer.canSpend(AGENT, 5, 'api_costs');
145
- expect(result.allowed).toBe(false);
146
- expect(result.reason).toContain('"api_costs" budget');
147
- });
148
-
149
- it('should allow categories without specific budgets', async () => {
150
- const result = await enforcer.canSpend(AGENT, 15, 'shopping');
151
- expect(result.allowed).toBe(true);
152
- });
153
- });
154
-
155
- /* ── Blocked categories ────────────────────────────────────────── */
156
-
157
- describe('blocked categories', () => {
158
- it('should block spend in blocked category', async () => {
159
- const result = await enforcer.canSpend(AGENT, 1, 'defi');
160
- expect(result.allowed).toBe(false);
161
- expect(result.reason).toContain('blocked');
162
- });
163
-
164
- it('should allow spend in non-blocked category', async () => {
165
- const result = await enforcer.canSpend(AGENT, 5, 'transfers');
166
- expect(result.allowed).toBe(true);
167
- });
168
- });
169
-
170
- /* ── Address whitelist/blacklist ────────────────────────────────── */
171
-
172
- describe('address restrictions', () => {
173
- it('should block blacklisted address', async () => {
174
- const result = await enforcer.canSpend(AGENT, 5, 'transfers', '0xBlocked1');
175
- expect(result.allowed).toBe(false);
176
- expect(result.reason).toContain('blocked');
177
- });
178
-
179
- it('should allow whitelisted address', async () => {
180
- const result = await enforcer.canSpend(AGENT, 5, 'transfers', '0xAllowed1');
181
- expect(result.allowed).toBe(true);
182
- });
183
-
184
- it('should block address not in whitelist', async () => {
185
- const result = await enforcer.canSpend(AGENT, 5, 'transfers', '0xRandom');
186
- expect(result.allowed).toBe(false);
187
- expect(result.reason).toContain('not in the allowed list');
188
- });
189
-
190
- it('should allow any address when no whitelist is configured', async () => {
191
- policy.allowedAddresses = undefined;
192
- enforcer = new SpendingPolicyEnforcer(policy, ledger);
193
-
194
- const result = await enforcer.canSpend(AGENT, 5, 'transfers', '0xAnything');
195
- expect(result.allowed).toBe(true);
196
- });
197
- });
198
-
199
- /* ── Approval threshold ────────────────────────────────────────── */
200
-
201
- describe('approval threshold', () => {
202
- it('should flag requiresApproval for amounts above threshold', async () => {
203
- const result = await enforcer.canSpend(AGENT, 18, 'transfers', '0xAllowed1');
204
- expect(result.allowed).toBe(true);
205
- expect(result.requiresApproval).toBe(true);
206
- });
207
-
208
- it('should not flag requiresApproval for amounts at or below threshold', async () => {
209
- const result = await enforcer.canSpend(AGENT, 15, 'transfers', '0xAllowed1');
210
- expect(result.allowed).toBe(true);
211
- expect(result.requiresApproval).toBe(false);
212
- });
213
- });
214
-
215
- /* ── Spend recording ───────────────────────────────────────────── */
216
-
217
- describe('recordSpend', () => {
218
- it('should insert daily and monthly entries into the ledger', async () => {
219
- await enforcer.recordSpend(AGENT, 10, 'api_costs');
220
-
221
- // Should produce 2 entries: one daily, one monthly
222
- expect(ledger.entries.length).toBe(2);
223
- expect(ledger.entries[0].periodKey).toMatch(/^\d{4}-\d{2}-\d{2}$/); // YYYY-MM-DD
224
- expect(ledger.entries[1].periodKey).toMatch(/^\d{4}-\d{2}$/); // YYYY-MM
225
- expect(ledger.entries[0].amountUsd).toBe(10);
226
- expect(ledger.entries[0].category).toBe('api_costs');
227
- });
228
-
229
- it('should accumulate across multiple spends', async () => {
230
- await enforcer.recordSpend(AGENT, 5, 'transfers');
231
- await enforcer.recordSpend(AGENT, 8, 'transfers');
232
-
233
- const today = new Date().toISOString().slice(0, 10);
234
- const sum = await ledger.sumByPeriod(AGENT, today);
235
- expect(sum).toBe(13);
236
- });
237
- });
238
-
239
- /* ── Check order priority ──────────────────────────────────────── */
240
-
241
- describe('check order', () => {
242
- it('should block on category before checking limits', async () => {
243
- const result = await enforcer.canSpend(AGENT, 1, 'defi', '0xAllowed1');
244
- expect(result.allowed).toBe(false);
245
- expect(result.reason).toContain('blocked');
246
- });
247
-
248
- it('should block on blacklist before checking limits', async () => {
249
- const result = await enforcer.canSpend(AGENT, 1, 'transfers', '0xBlocked1');
250
- expect(result.allowed).toBe(false);
251
- expect(result.reason).toContain('blocked');
252
- });
253
- });
254
- });
@@ -1,242 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Integration tests for the card subsystem within the wallet extension pack.
4
- *
5
- * Tests the full lifecycle: extension pack creation with card enabled →
6
- * card tool registration → card issuance → status → spending summary →
7
- * freeze → unfreeze → close. Also verifies backwards compatibility
8
- * when card is disabled.
9
- */
10
-
11
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
12
- import { createExtensionPack } from '../src/index.js';
13
- import type { ExtensionPack } from '../src/index.js';
14
-
15
- /* ------------------------------------------------------------------ */
16
- /* Mock fetch for Lithic API calls */
17
- /* ------------------------------------------------------------------ */
18
-
19
- const mockFetch = vi.fn();
20
-
21
- beforeEach(() => {
22
- vi.stubGlobal('fetch', mockFetch);
23
- mockFetch.mockReset();
24
-
25
- // Default mock for Lithic createCard
26
- mockFetch.mockResolvedValue({
27
- ok: true,
28
- status: 200,
29
- json: async () => ({
30
- token: 'integration-card-token',
31
- last_four: '8888',
32
- type: 'VIRTUAL',
33
- state: 'OPEN',
34
- spend_limit: 50000,
35
- spend_limit_duration: 'MONTHLY',
36
- pan: '4111111111118888',
37
- cvv: '789',
38
- exp_month: '03',
39
- exp_year: '2030',
40
- funding: { token: 'fund-1', type: 'DEPOSITORY_CHECKING' },
41
- }),
42
- });
43
- });
44
-
45
- afterEach(() => {
46
- vi.unstubAllGlobals();
47
- });
48
-
49
- /* ------------------------------------------------------------------ */
50
- /* Tests */
51
- /* ------------------------------------------------------------------ */
52
-
53
- describe('card integration', () => {
54
-
55
- /* ── Extension pack with cards disabled ────────────────────────── */
56
-
57
- describe('card disabled (backwards compatibility)', () => {
58
- it('should create pack with only 3 crypto tools when card not configured', () => {
59
- const pack = createExtensionPack({
60
- options: { enabled: true, chains: ['solana'] },
61
- });
62
-
63
- expect(pack.descriptors.length).toBe(3);
64
- expect(pack.descriptors.every(d => d.kind === 'tool')).toBe(true);
65
- });
66
-
67
- it('should create pack with only 3 crypto tools when card.enabled=false', () => {
68
- const pack = createExtensionPack({
69
- options: { enabled: true, chains: ['solana'], card: { enabled: false, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false } },
70
- });
71
-
72
- expect(pack.descriptors.length).toBe(3);
73
- });
74
- });
75
-
76
- /* ── Extension pack with cards enabled ─────────────────────────── */
77
-
78
- describe('card enabled', () => {
79
- let pack: ExtensionPack;
80
-
81
- beforeEach(() => {
82
- pack = createExtensionPack({
83
- options: {
84
- enabled: true,
85
- chains: ['solana'],
86
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
87
- },
88
- secrets: { LITHIC_API_KEY: 'test-lithic-key' },
89
- });
90
- });
91
-
92
- it('should create pack with 9 tools (3 crypto + 6 card)', () => {
93
- expect(pack.descriptors.length).toBe(9);
94
- expect(pack.descriptors.every(d => d.kind === 'tool')).toBe(true);
95
- });
96
-
97
- it('should register all 6 card tools', () => {
98
- const toolNames = pack.descriptors.map(d => d.id);
99
- expect(toolNames).toContain('issue_virtual_card');
100
- expect(toolNames).toContain('card_status');
101
- expect(toolNames).toContain('freeze_card');
102
- expect(toolNames).toContain('unfreeze_card');
103
- expect(toolNames).toContain('card_spending_summary');
104
- expect(toolNames).toContain('pay_with_card');
105
- });
106
-
107
- it('should still register all 3 crypto tools', () => {
108
- const toolNames = pack.descriptors.map(d => d.id);
109
- expect(toolNames).toContain('check_wallet_balance');
110
- expect(toolNames).toContain('send_crypto');
111
- expect(toolNames).toContain('wallet_transaction_history');
112
- });
113
-
114
- it('should set version to 0.2.0', () => {
115
- expect(pack.version).toBe('0.2.0');
116
- });
117
-
118
- it('should call onActivate with card mention', async () => {
119
- const logs: string[] = [];
120
- const p = createExtensionPack({
121
- options: {
122
- enabled: true,
123
- chains: ['solana'],
124
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
125
- },
126
- logger: { info: (m: string) => logs.push(m) },
127
- });
128
-
129
- await p.onActivate?.();
130
- expect(logs.length).toBe(1);
131
- expect(logs[0]).toContain('virtual card');
132
- });
133
- });
134
-
135
- /* ── Card tool execution lifecycle ─────────────────────────────── */
136
-
137
- describe('card tool lifecycle', () => {
138
- const CTX = {
139
- gmiId: 'integration-agent',
140
- personaId: 'persona-1',
141
- userContext: { userId: 'user-1' } as any,
142
- };
143
-
144
- function getTools(pack: ExtensionPack) {
145
- const tools = new Map<string, any>();
146
- for (const desc of pack.descriptors) {
147
- if (desc.kind === 'tool') tools.set((desc.payload as any).name, desc.payload);
148
- }
149
- return tools;
150
- }
151
-
152
- it('should execute issue → status → freeze → unfreeze → close lifecycle', async () => {
153
- const pack = createExtensionPack({
154
- options: {
155
- enabled: true,
156
- chains: ['solana'],
157
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
158
- },
159
- secrets: { LITHIC_API_KEY: 'test-key' },
160
- });
161
-
162
- const tools = getTools(pack);
163
-
164
- // Issue card
165
- const issueResult = await tools.get('issue_virtual_card').execute({}, CTX);
166
- expect(issueResult.success).toBe(true);
167
- expect(issueResult.output.last4).toBe('8888');
168
-
169
- // Check status
170
- const statusResult = await tools.get('card_status').execute({}, CTX);
171
- expect(statusResult.success).toBe(true);
172
- expect(statusResult.output.state).toBe('OPEN');
173
-
174
- // Spending summary (empty)
175
- const spendingResult = await tools.get('card_spending_summary').execute({}, CTX);
176
- expect(spendingResult.success).toBe(true);
177
- expect(spendingResult.output.totalUsd).toBe(0);
178
-
179
- // Freeze
180
- const freezeResult = await tools.get('freeze_card').execute({}, CTX);
181
- expect(freezeResult.success).toBe(true);
182
- expect(freezeResult.output.state).toBe('PAUSED');
183
-
184
- // Unfreeze
185
- const unfreezeResult = await tools.get('unfreeze_card').execute({}, CTX);
186
- expect(unfreezeResult.success).toBe(true);
187
- expect(unfreezeResult.output.state).toBe('OPEN');
188
- });
189
-
190
- it('should return error from card tools when no card exists', async () => {
191
- const pack = createExtensionPack({
192
- options: {
193
- enabled: true,
194
- chains: ['solana'],
195
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
196
- },
197
- });
198
-
199
- const tools = getTools(pack);
200
-
201
- const statusResult = await tools.get('card_status').execute({}, CTX);
202
- expect(statusResult.success).toBe(false);
203
- expect(statusResult.error).toContain('No card found');
204
-
205
- const freezeResult = await tools.get('freeze_card').execute({}, CTX);
206
- expect(freezeResult.success).toBe(false);
207
-
208
- const spendingResult = await tools.get('card_spending_summary').execute({}, CTX);
209
- expect(spendingResult.success).toBe(false);
210
- });
211
-
212
- it('all tools should have category=wallet', () => {
213
- const pack = createExtensionPack({
214
- options: {
215
- enabled: true,
216
- chains: ['solana'],
217
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
218
- },
219
- });
220
-
221
- for (const desc of pack.descriptors) {
222
- expect((desc.payload as any).category).toBe('wallet');
223
- }
224
- });
225
-
226
- it('all tools should have valid inputSchema', () => {
227
- const pack = createExtensionPack({
228
- options: {
229
- enabled: true,
230
- chains: ['solana'],
231
- card: { enabled: true, provider: 'lithic', defaultSpendLimitUsd: 500, allowPhysical: false },
232
- },
233
- });
234
-
235
- for (const desc of pack.descriptors) {
236
- const tool = desc.payload as any;
237
- expect(tool.inputSchema).toBeTruthy();
238
- expect(tool.inputSchema.type).toBe('object');
239
- }
240
- });
241
- });
242
- });