1+ import { describe , it , expect , vi , beforeEach } from 'vitest' ;
2+ import { listApps , appInfo , createApp , updateApp , deleteApp } from '../src/commands/apps.js' ;
3+ import chalk from 'chalk' ;
4+ import Table from 'cli-table3' ;
5+ import * as PuterModule from '../src/modules/PuterModule.js' ;
6+ import * as subdomains from '../src/commands/subdomains.js' ;
7+ import * as sites from '../src/commands/sites.js' ;
8+ import * as files from '../src/commands/files.js' ;
9+ import * as auth from '../src/commands/auth.js' ;
10+ import * as commons from '../src/commons.js' ;
11+ import * as utils from '../src/utils.js' ;
12+ import crypto from '../src/crypto.js' ;
13+
14+ // Mock console to prevent actual logging
15+ vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } ) ;
16+ vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
17+
18+ vi . mock ( "conf" , ( ) => {
19+ const Conf = vi . fn ( ( ) => ( {
20+ get : vi . fn ( ) ,
21+ set : vi . fn ( ) ,
22+ clear : vi . fn ( ) ,
23+ } ) ) ;
24+ return { default : Conf } ;
25+ } ) ;
26+
27+ // Mock dependencies
28+ vi . mock ( 'chalk' , ( ) => ( {
29+ default : {
30+ green : vi . fn ( text => text ) ,
31+ red : vi . fn ( text => text ) ,
32+ dim : vi . fn ( text => text ) ,
33+ yellow : vi . fn ( text => text ) ,
34+ cyan : vi . fn ( text => text ) ,
35+ cyanBright : vi . fn ( text => text ) ,
36+ bold : vi . fn ( text => text ) ,
37+ }
38+ } ) ) ;
39+ vi . mock ( 'cli-table3' ) ;
40+ vi . mock ( 'node-fetch' ) ;
41+ vi . mock ( '../src/modules/PuterModule.js' ) ;
42+ vi . mock ( '../src/commands/subdomains.js' ) ;
43+ vi . mock ( '../src/commands/sites.js' ) ;
44+ vi . mock ( '../src/commands/files.js' ) ;
45+ vi . mock ( '../src/commands/auth.js' ) ;
46+ vi . mock ( '../src/commons.js' ) ;
47+ vi . mock ( '../src/utils.js' ) ;
48+ vi . mock ( '../src/crypto.js' ) ;
49+
50+ const mockPuter = {
51+ apps : {
52+ list : vi . fn ( ) ,
53+ get : vi . fn ( ) ,
54+ create : vi . fn ( ) ,
55+ update : vi . fn ( ) ,
56+ delete : vi . fn ( ) ,
57+ } ,
58+ fs : {
59+ mkdir : vi . fn ( ) ,
60+ } ,
61+ } ;
62+
63+ describe ( 'apps.js' , ( ) => {
64+ let mockTable ;
65+
66+ beforeEach ( ( ) => {
67+ vi . clearAllMocks ( ) ;
68+ vi . spyOn ( PuterModule , 'getPuter' ) . mockReturnValue ( mockPuter ) ;
69+ vi . spyOn ( auth , 'getCurrentDirectory' ) . mockReturnValue ( '/testuser' ) ;
70+ vi . spyOn ( commons , 'isValidAppName' ) . mockReturnValue ( true ) ;
71+ vi . spyOn ( commons , 'resolvePath' ) . mockImplementation ( ( _ , newPath ) => newPath ) ;
72+ vi . spyOn ( crypto , 'randomUUID' ) . mockReturnValue ( 'mock-uuid' ) ;
73+
74+ mockTable = {
75+ push : vi . fn ( ) ,
76+ toString : vi . fn ( ( ) => 'table string' ) ,
77+ } ;
78+ Table . mockImplementation ( ( ) => mockTable ) ;
79+ } ) ;
80+
81+ describe ( 'listApps' , ( ) => {
82+ it ( 'should list apps successfully' , async ( ) => {
83+ const mockApps = [
84+ { title : 'App 1' , name : 'app-1' , created_at : new Date ( ) . toISOString ( ) , index_url : 'https://app-1.puter.site' , stats : { open_count : 10 , user_count : 5 } } ,
85+ { title : 'App 2' , name : 'app-2' , created_at : new Date ( ) . toISOString ( ) , index_url : 'https://app-2.puter.site' , stats : { open_count : 20 , user_count : 15 } } ,
86+ ] ;
87+ mockPuter . apps . list . mockResolvedValue ( mockApps ) ;
88+ vi . spyOn ( utils , 'formatDate' ) . mockReturnValue ( 'formatted-date' ) ;
89+
90+ await listApps ( ) ;
91+
92+ expect ( mockPuter . apps . list ) . toHaveBeenCalled ( ) ;
93+ expect ( mockTable . push ) . toHaveBeenCalledTimes ( 2 ) ;
94+ expect ( console . log ) . toHaveBeenCalledWith ( 'table string' ) ;
95+ } ) ;
96+
97+ it ( 'should handle API error when listing apps' , async ( ) => {
98+ mockPuter . apps . list . mockRejectedValue ( new Error ( 'API Error' ) ) ;
99+ await listApps ( ) ;
100+ expect ( console . error ) . toHaveBeenCalledWith ( 'Failed to list apps. Error: API Error' ) ;
101+ } ) ;
102+ } ) ;
103+
104+ describe ( 'appInfo' , ( ) => {
105+ it ( 'should show app info successfully' , async ( ) => {
106+ const mockApp = { name : 'test-app' , title : 'Test App' } ;
107+ mockPuter . apps . get . mockResolvedValue ( mockApp ) ;
108+ vi . spyOn ( utils , 'displayNonNullValues' ) . mockImplementation ( ( ) => { } ) ;
109+
110+ await appInfo ( [ 'test-app' ] ) ;
111+
112+ expect ( mockPuter . apps . get ) . toHaveBeenCalledWith ( 'test-app' ) ;
113+ expect ( utils . displayNonNullValues ) . toHaveBeenCalledWith ( mockApp ) ;
114+ } ) ;
115+
116+ it ( 'should show usage if no app name is provided' , async ( ) => {
117+ await appInfo ( [ ] ) ;
118+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . red ( 'Usage: app <name>' ) ) ;
119+ } ) ;
120+
121+ it ( 'should handle app not found' , async ( ) => {
122+ mockPuter . apps . get . mockResolvedValue ( null ) ;
123+ await appInfo ( [ 'non-existent-app' ] ) ;
124+ expect ( console . error ) . toHaveBeenCalledWith ( chalk . red ( 'Could not find this app.' ) ) ;
125+ } ) ;
126+ } ) ;
127+
128+ describe ( 'createApp' , ( ) => {
129+ beforeEach ( ( ) => {
130+ mockPuter . apps . create . mockResolvedValue ( { uid : 'app-uid' , name : 'new-app' , owner : { username : 'testuser' } } ) ;
131+ mockPuter . fs . mkdir . mockResolvedValue ( { uid : 'dir-uid' , name : 'app-mock-uuid' } ) ;
132+ vi . spyOn ( subdomains , 'createSubdomain' ) . mockResolvedValue ( true ) ;
133+ vi . spyOn ( files , 'createFile' ) . mockResolvedValue ( true ) ;
134+ mockPuter . apps . update . mockResolvedValue ( true ) ;
135+ } )
136+ it ( 'should create an app successfully' , async ( ) => {
137+ await createApp ( { name : 'new-app' } ) ;
138+
139+ expect ( mockPuter . apps . create ) . toHaveBeenCalled ( ) ;
140+ expect ( mockPuter . fs . mkdir ) . toHaveBeenCalled ( ) ;
141+ expect ( subdomains . createSubdomain ) . toHaveBeenCalled ( ) ;
142+ expect ( files . createFile ) . toHaveBeenCalled ( ) ;
143+ expect ( mockPuter . apps . update ) . toHaveBeenCalled ( ) ;
144+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . green ( 'App deployed successfully at:' ) ) ;
145+ } ) ;
146+
147+ it ( 'should show usage if app name is invalid' , async ( ) => {
148+ vi . spyOn ( commons , 'isValidAppName' ) . mockReturnValue ( false ) ;
149+ await createApp ( { name : 'invalid-' } ) ;
150+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . red ( 'Usage: app:create <name> <directory>' ) ) ;
151+ } ) ;
152+ } ) ;
153+
154+ describe ( 'updateApp' , ( ) => {
155+ it ( 'should update an app successfully' , async ( ) => {
156+ mockPuter . apps . get . mockResolvedValue ( { uid : 'app-uid' , name : 'test-app' , owner : { username : 'testuser' } , index_url : 'https://test.puter.site' } ) ;
157+ vi . spyOn ( files , 'pathExists' ) . mockResolvedValue ( true ) ;
158+ vi . spyOn ( subdomains , 'getSubdomains' ) . mockResolvedValue ( [ { root_dir : { dirname : 'app-uid' , path : '/path/to/app' } , uid : 'sub-uid' } ] ) ;
159+ vi . spyOn ( files , 'listRemoteFiles' ) . mockResolvedValue ( [ { name : 'index.html' } ] ) ;
160+ vi . spyOn ( files , 'copyFile' ) . mockResolvedValue ( true ) ;
161+ vi . spyOn ( files , 'removeFileOrDirectory' ) . mockResolvedValue ( true ) ;
162+
163+ await updateApp ( [ 'test-app' , '.' ] ) ;
164+
165+ expect ( mockPuter . apps . get ) . toHaveBeenCalledWith ( 'test-app' ) ;
166+ expect ( files . listRemoteFiles ) . toHaveBeenCalled ( ) ;
167+ expect ( files . copyFile ) . toHaveBeenCalled ( ) ;
168+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . green ( 'App updated successfully at:' ) ) ;
169+ } ) ;
170+ } ) ;
171+
172+ describe ( 'deleteApp' , ( ) => {
173+ it ( 'should delete an app successfully' , async ( ) => {
174+ mockPuter . apps . get . mockResolvedValue ( { uid : 'app-uid' , name : 'test-app' , title : 'Test App' , created_at : new Date ( ) . toISOString ( ) } ) ;
175+ mockPuter . apps . delete . mockResolvedValue ( true ) ;
176+ vi . spyOn ( subdomains , 'getSubdomains' ) . mockResolvedValue ( [ { root_dir : { dirname : 'app-uid' } , uid : 'sub-uid' } ] ) ;
177+ vi . spyOn ( sites , 'deleteSite' ) . mockResolvedValue ( true ) ;
178+
179+ const result = await deleteApp ( 'test-app' ) ;
180+
181+ expect ( result ) . toBe ( true ) ;
182+ expect ( mockPuter . apps . delete ) . toHaveBeenCalledWith ( 'test-app' ) ;
183+ expect ( sites . deleteSite ) . toHaveBeenCalled ( ) ;
184+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . green ( 'App "test-app" deleted successfully!' ) ) ;
185+ } ) ;
186+
187+ it ( 'should return false if app not found' , async ( ) => {
188+ mockPuter . apps . get . mockResolvedValue ( null ) ;
189+ const result = await deleteApp ( 'non-existent-app' ) ;
190+ expect ( result ) . toBe ( false ) ;
191+ expect ( console . log ) . toHaveBeenCalledWith ( chalk . red ( 'App "non-existent-app" not found.' ) ) ;
192+ } ) ;
193+ } ) ;
194+ } ) ;
0 commit comments