1+ import { describe , expect , test , beforeEach , mock } from 'bun:test'
2+ import { checkStartupPermissions } from './startup-checks'
3+
4+ // Mock the fs promises module
5+ const mockStat = mock ( )
6+ const mockWriteFile = mock ( )
7+ const mockReadFile = mock ( )
8+ const mockUnlink = mock ( )
9+
10+ mock . module ( 'fs' , ( ) => ( {
11+ promises : {
12+ stat : mockStat ,
13+ writeFile : mockWriteFile ,
14+ readFile : mockReadFile ,
15+ unlink : mockUnlink ,
16+ } ,
17+ } ) )
18+
19+ describe ( 'checkStartupPermissions' , ( ) => {
20+ beforeEach ( ( ) => {
21+ // Reset all mocks before each test
22+ mockStat . mockReset ( )
23+ mockWriteFile . mockReset ( )
24+ mockReadFile . mockReset ( )
25+ mockUnlink . mockReset ( )
26+ } )
27+
28+ test ( 'should return success when directory exists and has proper permissions' , async ( ) => {
29+ // Mock successful directory stat
30+ mockStat . mockResolvedValue ( {
31+ isDirectory : ( ) => true ,
32+ } )
33+
34+ // Mock successful file operations
35+ mockWriteFile . mockResolvedValue ( undefined )
36+ mockReadFile . mockResolvedValue ( 'permission-test' )
37+ mockUnlink . mockResolvedValue ( undefined )
38+
39+ const result = await checkStartupPermissions ( )
40+
41+ expect ( result ) . toEqual ( { success : true } )
42+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
43+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
44+ expect ( mockReadFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'utf8' )
45+ expect ( mockUnlink ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' )
46+ } )
47+
48+ test ( 'should return error when directory does not exist' , async ( ) => {
49+ mockStat . mockRejectedValue ( new Error ( 'ENOENT: no such file or directory' ) )
50+
51+ const result = await checkStartupPermissions ( )
52+
53+ expect ( result ) . toEqual ( {
54+ success : false ,
55+ error : {
56+ path : 'data' ,
57+ message : 'Data directory \'data\' does not exist or is not accessible. Check volume mounts and permissions.' ,
58+ type : 'writable_data_dir'
59+ }
60+ } )
61+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
62+ expect ( mockWriteFile ) . not . toHaveBeenCalled ( )
63+ } )
64+
65+ test ( 'should return error when path exists but is not a directory' , async ( ) => {
66+ // Mock path exists but is a file, not directory
67+ mockStat . mockResolvedValue ( {
68+ isDirectory : ( ) => false ,
69+ } )
70+
71+ const result = await checkStartupPermissions ( )
72+
73+ expect ( result ) . toEqual ( {
74+ success : false ,
75+ error : {
76+ path : 'data' ,
77+ message : 'Path \'data\' exists but is not a directory. Please ensure the data directory is properly configured.' ,
78+ type : 'writable_data_dir'
79+ }
80+ } )
81+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
82+ expect ( mockWriteFile ) . not . toHaveBeenCalled ( )
83+ } )
84+
85+ test ( 'should return error when write permission fails' , async ( ) => {
86+ // Mock successful directory stat
87+ mockStat . mockResolvedValue ( {
88+ isDirectory : ( ) => true ,
89+ } )
90+
91+ // Mock write failure
92+ mockWriteFile . mockRejectedValue ( new Error ( 'EACCES: permission denied' ) )
93+
94+ const result = await checkStartupPermissions ( )
95+
96+ expect ( result ) . toEqual ( {
97+ success : false ,
98+ error : {
99+ path : 'data' ,
100+ message : 'Insufficient read/write permissions for data directory \'data\'. Check file permissions and ownership.' ,
101+ type : 'writable_data_dir'
102+ }
103+ } )
104+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
105+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
106+ expect ( mockReadFile ) . not . toHaveBeenCalled ( )
107+ } )
108+
109+ test ( 'should return error when read permission fails' , async ( ) => {
110+ // Mock successful directory stat and write
111+ mockStat . mockResolvedValue ( {
112+ isDirectory : ( ) => true ,
113+ } )
114+ mockWriteFile . mockResolvedValue ( undefined )
115+
116+ // Mock read failure
117+ mockReadFile . mockRejectedValue ( new Error ( 'EACCES: permission denied' ) )
118+
119+ const result = await checkStartupPermissions ( )
120+
121+ expect ( result ) . toEqual ( {
122+ success : false ,
123+ error : {
124+ path : 'data' ,
125+ message : 'Insufficient read/write permissions for data directory \'data\'. Check file permissions and ownership.' ,
126+ type : 'writable_data_dir'
127+ }
128+ } )
129+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
130+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
131+ expect ( mockReadFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'utf8' )
132+ } )
133+
134+ test ( 'should return error when read content does not match written content' , async ( ) => {
135+ // Mock successful directory stat and write
136+ mockStat . mockResolvedValue ( {
137+ isDirectory : ( ) => true ,
138+ } )
139+ mockWriteFile . mockResolvedValue ( undefined )
140+
141+ // Mock read with different content
142+ mockReadFile . mockResolvedValue ( 'different-content' )
143+
144+ const result = await checkStartupPermissions ( )
145+
146+ expect ( result ) . toEqual ( {
147+ success : false ,
148+ error : {
149+ path : 'data' ,
150+ message : 'Data integrity check failed in \'data\'. File system may be corrupted or have inconsistent behavior.' ,
151+ type : 'writable_data_dir'
152+ }
153+ } )
154+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
155+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
156+ expect ( mockReadFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'utf8' )
157+ expect ( mockUnlink ) . not . toHaveBeenCalled ( )
158+ } )
159+
160+ test ( 'should return error when cleanup (unlink) fails' , async ( ) => {
161+ // Mock successful directory stat, write, and read
162+ mockStat . mockResolvedValue ( {
163+ isDirectory : ( ) => true ,
164+ } )
165+ mockWriteFile . mockResolvedValue ( undefined )
166+ mockReadFile . mockResolvedValue ( 'permission-test' )
167+
168+ // Mock cleanup failure
169+ mockUnlink . mockRejectedValue ( new Error ( 'EACCES: permission denied' ) )
170+
171+ const result = await checkStartupPermissions ( )
172+
173+ // Should return error since cleanup failed and is part of the try-catch block
174+ expect ( result ) . toEqual ( {
175+ success : false ,
176+ error : {
177+ path : 'data' ,
178+ message : 'Insufficient read/write permissions for data directory \'data\'. Check file permissions and ownership.' ,
179+ type : 'writable_data_dir'
180+ }
181+ } )
182+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
183+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
184+ expect ( mockReadFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'utf8' )
185+ expect ( mockUnlink ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' )
186+ } )
187+
188+ test ( 'should use correct file paths' , async ( ) => {
189+ // Mock successful operations
190+ mockStat . mockResolvedValue ( {
191+ isDirectory : ( ) => true ,
192+ } )
193+ mockWriteFile . mockResolvedValue ( undefined )
194+ mockReadFile . mockResolvedValue ( 'permission-test' )
195+ mockUnlink . mockResolvedValue ( undefined )
196+
197+ await checkStartupPermissions ( )
198+
199+ // Verify the correct paths are used
200+ expect ( mockStat ) . toHaveBeenCalledWith ( 'data' )
201+ expect ( mockWriteFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'permission-test' )
202+ expect ( mockReadFile ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' , 'utf8' )
203+ expect ( mockUnlink ) . toHaveBeenCalledWith ( 'data/.habittrove-permission-test' )
204+ } )
205+ } )
0 commit comments