1313
1414namespace CodeIgniter \Commands ;
1515
16+ use CodeIgniter \CLI \CLI ;
1617use CodeIgniter \Test \CIUnitTestCase ;
18+ use CodeIgniter \Test \Mock \MockInputOutput ;
1719use CodeIgniter \Test \StreamFilterTrait ;
1820use PHPUnit \Framework \Attributes \Group ;
21+ use PHPUnit \Framework \Attributes \RequiresOperatingSystem ;
1922
2023/**
2124 * @internal
@@ -34,37 +37,145 @@ protected function setUp(): void
3437 // test runs on other tests may log errors since default threshold
3538 // is now 4, so set this to a safe distance
3639 $ this ->date = date ('Y-m-d ' , strtotime ('+1 year ' ));
40+
41+ command ('logs:clear --force ' );
42+ $ this ->resetStreamFilterBuffer ();
43+
44+ $ this ->createDummyLogFiles ();
45+ }
46+
47+ protected function tearDown (): void
48+ {
49+ command ('logs:clear --force ' );
50+ $ this ->resetStreamFilterBuffer ();
51+
52+ parent ::tearDown ();
3753 }
3854
39- protected function createDummyLogFiles (): void
55+ private function createDummyLogFiles (): void
4056 {
4157 $ date = $ this ->date ;
4258 $ path = WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ date }.log " ;
4359
4460 // create 10 dummy log files
4561 for ($ i = 0 ; $ i < 10 ; $ i ++) {
4662 $ newDate = date ('Y-m-d ' , strtotime ("+1 year - {$ i } day " ));
47- $ path = str_replace ($ date , $ newDate , $ path );
63+
64+ $ path = str_replace ($ date , $ newDate , $ path );
4865 file_put_contents ($ path , 'Lorem ipsum ' );
4966
5067 $ date = $ newDate ;
5168 }
5269 }
5370
54- public function testClearLogsWorks (): void
71+ public function testClearLogsUsingForce (): void
5572 {
56- // test clean logs dir
73+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
74+
75+ command ('logs:clear --force ' );
76+
5777 $ this ->assertFileDoesNotExist (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
78+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . 'index.html ' );
79+ $ this ->assertSame ("Logs cleared. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ this ->getStreamFilterBuffer ()));
80+ }
5881
59- // test dir is now populated with logs
60- $ this ->createDummyLogFiles ();
82+ public function testClearLogsAbortsClearWithoutForce (): void
83+ {
84+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
85+
86+ $ io = new MockInputOutput ();
87+ $ io ->setInputs (['n ' ]);
88+ CLI ::setInputOutput ($ io );
89+
90+ command ('logs:clear ' );
91+
92+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
93+ $ this ->assertSame (
94+ <<<'EOT'
95+ Deleting logs aborted.
96+ If you want, use the "--force" option to force delete all log files.
97+
98+ EOT,
99+ preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 ) . $ io ->getOutput (3 )),
100+ );
101+ }
102+
103+ public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer (): void
104+ {
105+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
106+
107+ $ io = new MockInputOutput ();
108+ $ io ->setInputs (['' ]);
109+ CLI ::setInputOutput ($ io );
110+
111+ command ('logs:clear ' );
112+
113+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
114+ $ this ->assertSame (
115+ <<<'EOT'
116+ Deleting logs aborted.
117+ If you want, use the "--force" option to force delete all log files.
118+
119+ EOT,
120+ preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 ) . $ io ->getOutput (3 )),
121+ );
122+ }
123+
124+ public function testClearLogsWithoutForceButWithConfirmation (): void
125+ {
61126 $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
62127
63- command ('logs:clear -force ' );
64- $ result = $ this ->getStreamFilterBuffer ();
128+ $ io = new MockInputOutput ();
129+ $ io ->setInputs (['y ' ]);
130+ CLI ::setInputOutput ($ io );
131+
132+ command ('logs:clear ' );
65133
66134 $ this ->assertFileDoesNotExist (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
67- $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . 'index.html ' );
68- $ this ->assertStringContainsString ('Logs cleared. ' , $ result );
135+ $ this ->assertSame ("Logs cleared. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 )));
136+ }
137+
138+ #[RequiresOperatingSystem('Darwin|Linux ' )]
139+ public function testClearLogsFailsOnChmodFailure (): void
140+ {
141+ $ path = WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " ;
142+ file_put_contents ($ path , 'Lorem ipsum ' );
143+
144+ // Attempt to make the file itself undeletable by setting the
145+ // immutable/uchg flag on supported platforms.
146+ $ immutableSet = false ;
147+ if (str_starts_with (PHP_OS , 'Darwin ' )) {
148+ @exec (sprintf ('chflags uchg %s ' , escapeshellarg ($ path )), $ output , $ rc );
149+ $ immutableSet = $ rc === 0 ;
150+ } else {
151+ // Try chattr on Linux
152+ @exec ('which chattr ' , $ whichOut , $ whichRc );
153+ if ($ whichRc === 0 ) {
154+ @exec (sprintf ('chattr +i %s ' , escapeshellarg ($ path )), $ output , $ rc );
155+ $ immutableSet = $ rc === 0 ;
156+ }
157+ }
158+
159+ if (! $ immutableSet ) {
160+ // Best-effort fallback: remove owner write bit. This may not
161+ // prevent unlink on all systems but gives some protection.
162+ chmod ($ path , 0400 );
163+ }
164+
165+ command ('logs:clear --force ' );
166+
167+ // Restore attributes so other tests are not affected.
168+ if ($ immutableSet ) {
169+ if (str_starts_with (PHP_OS , 'Darwin ' )) {
170+ @exec (sprintf ('chflags nouchg %s ' , escapeshellarg ($ path )));
171+ } else {
172+ @exec (sprintf ('chattr -i %s ' , escapeshellarg ($ path )));
173+ }
174+ } else {
175+ chmod ($ path , 0644 );
176+ }
177+
178+ $ this ->assertFileExists ($ path );
179+ $ this ->assertSame ("Error in deleting the logs files. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ this ->getStreamFilterBuffer ()));
69180 }
70181}
0 commit comments