1+ /**
2+ * Tests for CLI functionality
3+ */
4+
5+ import { execSync } from 'child_process' ;
6+ import { promises as fs } from 'fs' ;
7+ import path from 'path' ;
8+ import os from 'os' ;
9+
10+ describe ( 'CLI Commands' , ( ) => {
11+ let tempConfigDir : string ;
12+ let originalHome : string | undefined ;
13+
14+ beforeEach ( async ( ) => {
15+ // Create temporary directory for test config
16+ tempConfigDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'svm-pay-test-' ) ) ;
17+ originalHome = process . env . HOME ;
18+ process . env . HOME = tempConfigDir ;
19+
20+ // Mock console methods to reduce test noise
21+ jest . spyOn ( console , 'log' ) . mockImplementation ( ) ;
22+ jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
23+ jest . spyOn ( console , 'warn' ) . mockImplementation ( ) ;
24+ } ) ;
25+
26+ afterEach ( async ( ) => {
27+ // Restore environment
28+ if ( originalHome ) {
29+ process . env . HOME = originalHome ;
30+ }
31+
32+ // Clean up temp directory
33+ try {
34+ await fs . rmdir ( tempConfigDir , { recursive : true } ) ;
35+ } catch ( error ) {
36+ // Ignore cleanup errors
37+ }
38+
39+ jest . restoreAllMocks ( ) ;
40+ } ) ;
41+
42+ describe ( 'setup command' , ( ) => {
43+ it ( 'should show help when no arguments provided' , ( ) => {
44+ const result = execSync ( 'node dist/bin/svm-pay.js setup --help' , {
45+ encoding : 'utf8' ,
46+ cwd : process . cwd ( )
47+ } ) ;
48+
49+ expect ( result ) . toContain ( 'Set up your payment configuration' ) ;
50+ expect ( result ) . toContain ( '-k, --private-key' ) ;
51+ expect ( result ) . toContain ( '-a, --api-key' ) ;
52+ } ) ;
53+
54+ it ( 'should validate private key format' , ( ) => {
55+ try {
56+ execSync ( 'node dist/bin/svm-pay.js setup -k invalid-key' , {
57+ encoding : 'utf8' ,
58+ cwd : process . cwd ( ) ,
59+ env : { ...process . env , HOME : tempConfigDir }
60+ } ) ;
61+ fail ( 'Should have thrown an error for invalid private key' ) ;
62+ } catch ( error : any ) {
63+ expect ( error . status ) . toBe ( 1 ) ;
64+ }
65+ } ) ;
66+
67+ // Note: Testing with real private keys in unit tests is not recommended
68+ // In a real test environment, you'd use test keys or mock the validation
69+ } ) ;
70+
71+ describe ( 'balance command' , ( ) => {
72+ it ( 'should show error when not configured' , ( ) => {
73+ try {
74+ execSync ( 'node dist/bin/svm-pay.js balance' , {
75+ encoding : 'utf8' ,
76+ cwd : process . cwd ( ) ,
77+ env : { ...process . env , HOME : tempConfigDir }
78+ } ) ;
79+ fail ( 'Should have thrown an error for missing configuration' ) ;
80+ } catch ( error : any ) {
81+ expect ( error . status ) . toBe ( 1 ) ;
82+ }
83+ } ) ;
84+
85+ it ( 'should show help for balance command' , ( ) => {
86+ const result = execSync ( 'node dist/bin/svm-pay.js balance --help' , {
87+ encoding : 'utf8' ,
88+ cwd : process . cwd ( )
89+ } ) ;
90+
91+ expect ( result ) . toContain ( 'Check your current Solana wallet balance' ) ;
92+ } ) ;
93+ } ) ;
94+
95+ describe ( 'pay command' , ( ) => {
96+ it ( 'should show help for pay command' , ( ) => {
97+ const result = execSync ( 'node dist/bin/svm-pay.js pay --help' , {
98+ encoding : 'utf8' ,
99+ cwd : process . cwd ( )
100+ } ) ;
101+
102+ expect ( result ) . toContain ( 'Process a payment' ) ;
103+ expect ( result ) . toContain ( '-a, --amount' ) ;
104+ expect ( result ) . toContain ( '-t, --to' ) ;
105+ expect ( result ) . toContain ( '-f, --force' ) ;
106+ } ) ;
107+
108+ it ( 'should show dangerous warning for force flag' , ( ) => {
109+ const result = execSync ( 'node dist/bin/svm-pay.js pay --help' , {
110+ encoding : 'utf8' ,
111+ cwd : process . cwd ( )
112+ } ) ;
113+
114+ expect ( result ) . toContain ( '⚠️ DANGEROUS' ) ;
115+ expect ( result ) . toContain ( 'extreme caution' ) ;
116+ } ) ;
117+
118+ it ( 'should require configuration before payment' , ( ) => {
119+ try {
120+ execSync ( 'node dist/bin/svm-pay.js pay -a 1.0' , {
121+ encoding : 'utf8' ,
122+ cwd : process . cwd ( ) ,
123+ env : { ...process . env , HOME : tempConfigDir }
124+ } ) ;
125+ fail ( 'Should have thrown an error for missing configuration' ) ;
126+ } catch ( error : any ) {
127+ expect ( error . status ) . toBe ( 1 ) ;
128+ }
129+ } ) ;
130+ } ) ;
131+
132+ describe ( 'history command' , ( ) => {
133+ it ( 'should show help for history command' , ( ) => {
134+ const result = execSync ( 'node dist/bin/svm-pay.js history --help' , {
135+ encoding : 'utf8' ,
136+ cwd : process . cwd ( )
137+ } ) ;
138+
139+ expect ( result ) . toContain ( 'View your payment history' ) ;
140+ expect ( result ) . toContain ( '--limit' ) ;
141+ expect ( result ) . toContain ( '--all' ) ;
142+ } ) ;
143+
144+ it ( 'should handle empty history gracefully' , ( ) => {
145+ try {
146+ const result = execSync ( 'node dist/bin/svm-pay.js history' , {
147+ encoding : 'utf8' ,
148+ cwd : process . cwd ( ) ,
149+ env : { ...process . env , HOME : tempConfigDir }
150+ } ) ;
151+
152+ // Should not throw error, just show empty results
153+ expect ( result ) . toContain ( 'No payment history found' ) ;
154+ } catch ( error : any ) {
155+ // If it fails, it should be due to missing config, not empty history
156+ expect ( error . status ) . toBe ( 1 ) ;
157+ }
158+ } ) ;
159+ } ) ;
160+
161+ describe ( 'usage command' , ( ) => {
162+ it ( 'should show help for usage command' , ( ) => {
163+ const result = execSync ( 'node dist/bin/svm-pay.js usage --help' , {
164+ encoding : 'utf8' ,
165+ cwd : process . cwd ( )
166+ } ) ;
167+
168+ expect ( result ) . toContain ( 'Check your OpenRouter API usage' ) ;
169+ } ) ;
170+
171+ it ( 'should require configuration for API usage check' , ( ) => {
172+ try {
173+ execSync ( 'node dist/bin/svm-pay.js usage' , {
174+ encoding : 'utf8' ,
175+ cwd : process . cwd ( ) ,
176+ env : { ...process . env , HOME : tempConfigDir }
177+ } ) ;
178+ fail ( 'Should have thrown an error for missing configuration' ) ;
179+ } catch ( error : any ) {
180+ expect ( error . status ) . toBe ( 1 ) ;
181+ }
182+ } ) ;
183+ } ) ;
184+
185+ describe ( 'main CLI' , ( ) => {
186+ it ( 'should show help when no command provided' , ( ) => {
187+ const result = execSync ( 'node dist/bin/svm-pay.js --help' , {
188+ encoding : 'utf8' ,
189+ cwd : process . cwd ( )
190+ } ) ;
191+
192+ expect ( result ) . toContain ( 'CLI tool for managing Solana-based payments' ) ;
193+ expect ( result ) . toContain ( 'setup' ) ;
194+ expect ( result ) . toContain ( 'balance' ) ;
195+ expect ( result ) . toContain ( 'pay' ) ;
196+ expect ( result ) . toContain ( 'history' ) ;
197+ expect ( result ) . toContain ( 'usage' ) ;
198+ } ) ;
199+
200+ it ( 'should show version information' , ( ) => {
201+ const result = execSync ( 'node dist/bin/svm-pay.js --version' , {
202+ encoding : 'utf8' ,
203+ cwd : process . cwd ( )
204+ } ) ;
205+
206+ expect ( result . trim ( ) ) . toMatch ( / ^ \d + \. \d + \. \d + $ / ) ; // Semantic version format
207+ } ) ;
208+
209+ it ( 'should handle invalid commands gracefully' , ( ) => {
210+ try {
211+ execSync ( 'node dist/bin/svm-pay.js invalid-command' , {
212+ encoding : 'utf8' ,
213+ cwd : process . cwd ( )
214+ } ) ;
215+ fail ( 'Should have thrown an error for invalid command' ) ;
216+ } catch ( error : any ) {
217+ expect ( error . status ) . toBe ( 1 ) ;
218+ }
219+ } ) ;
220+ } ) ;
221+
222+ describe ( 'command validation' , ( ) => {
223+ it ( 'should validate Solana addresses in pay command' , ( ) => {
224+ // Test with obviously invalid address
225+ try {
226+ execSync ( 'node dist/bin/svm-pay.js pay -a 1.0 -t invalid-address -f' , {
227+ encoding : 'utf8' ,
228+ cwd : process . cwd ( ) ,
229+ env : { ...process . env , HOME : tempConfigDir }
230+ } ) ;
231+ fail ( 'Should have thrown an error for invalid address' ) ;
232+ } catch ( error : any ) {
233+ expect ( error . status ) . toBe ( 1 ) ;
234+ }
235+ } ) ;
236+
237+ it ( 'should validate amount format in pay command' , ( ) => {
238+ try {
239+ execSync ( 'node dist/bin/svm-pay.js pay -a invalid-amount' , {
240+ encoding : 'utf8' ,
241+ cwd : process . cwd ( ) ,
242+ env : { ...process . env , HOME : tempConfigDir }
243+ } ) ;
244+ fail ( 'Should have thrown an error for invalid amount' ) ;
245+ } catch ( error : any ) {
246+ expect ( error . status ) . toBe ( 1 ) ;
247+ }
248+ } ) ;
249+ } ) ;
250+
251+ describe ( 'environment variable support' , ( ) => {
252+ it ( 'should support SVM_PAY_PRIVATE_KEY environment variable' , async ( ) => {
253+ // Create minimal config without private key
254+ const configPath = path . join ( tempConfigDir , '.svm-pay' , 'config.json' ) ;
255+ await fs . mkdir ( path . dirname ( configPath ) , { recursive : true } ) ;
256+ await fs . writeFile ( configPath , JSON . stringify ( {
257+ openrouterApiKey : 'test-key' ,
258+ solanaRpcEndpoint : 'https://api.mainnet-beta.solana.com'
259+ } ) ) ;
260+
261+ // Test that environment variable is recognized
262+ // Note: This would fail in practice without a valid key, but tests the precedence
263+ try {
264+ execSync ( 'node dist/bin/svm-pay.js balance' , {
265+ encoding : 'utf8' ,
266+ cwd : process . cwd ( ) ,
267+ env : {
268+ ...process . env ,
269+ HOME : tempConfigDir ,
270+ SVM_PAY_PRIVATE_KEY : 'test-private-key'
271+ }
272+ } ) ;
273+ } catch ( error : any ) {
274+ // Should fail due to invalid key format, not missing key
275+ const stderr = error . stderr ?. toString ( ) || '' ;
276+ expect ( stderr ) . not . toContain ( 'Private key not configured' ) ;
277+ }
278+ } ) ;
279+ } ) ;
280+ } ) ;
0 commit comments