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,147 @@ 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+ CLI ::reset ();
53+
54+ parent ::tearDown ();
3755 }
3856
39- protected function createDummyLogFiles (): void
57+ private function createDummyLogFiles (): void
4058 {
4159 $ date = $ this ->date ;
4260 $ path = WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ date }.log " ;
4361
4462 // create 10 dummy log files
4563 for ($ i = 0 ; $ i < 10 ; $ i ++) {
4664 $ newDate = date ('Y-m-d ' , strtotime ("+1 year - {$ i } day " ));
47- $ path = str_replace ($ date , $ newDate , $ path );
65+
66+ $ path = str_replace ($ date , $ newDate , $ path );
4867 file_put_contents ($ path , 'Lorem ipsum ' );
4968
5069 $ date = $ newDate ;
5170 }
5271 }
5372
54- public function testClearLogsWorks (): void
73+ public function testClearLogsUsingForce (): void
5574 {
56- // test clean logs dir
75+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
76+
77+ command ('logs:clear --force ' );
78+
5779 $ this ->assertFileDoesNotExist (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
80+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . 'index.html ' );
81+ $ this ->assertSame ("Logs cleared. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ this ->getStreamFilterBuffer ()));
82+ }
83+
84+ public function testClearLogsAbortsClearWithoutForce (): void
85+ {
86+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
87+
88+ $ io = new MockInputOutput ();
89+ $ io ->setInputs (['n ' ]);
90+ CLI ::setInputOutput ($ io );
91+
92+ command ('logs:clear ' );
5893
59- // test dir is now populated with logs
60- $ this ->createDummyLogFiles ();
6194 $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
95+ $ this ->assertSame (
96+ <<<'EOT'
97+ Deleting logs aborted.
98+ If you want, use the "--force" option to force delete all log files.
99+
100+ EOT,
101+ preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 ) . $ io ->getOutput (3 )),
102+ );
103+ }
62104
63- command ('logs:clear -force ' );
64- $ result = $ this ->getStreamFilterBuffer ();
105+ public function testClearLogsAbortsClearWithoutForceWithDefaultAnswer (): void
106+ {
107+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
108+
109+ $ io = new MockInputOutput ();
110+ $ io ->setInputs (['' ]);
111+ CLI ::setInputOutput ($ io );
112+
113+ command ('logs:clear ' );
114+
115+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
116+ $ this ->assertSame (
117+ <<<'EOT'
118+ Deleting logs aborted.
119+ If you want, use the "--force" option to force delete all log files.
120+
121+ EOT,
122+ preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 ) . $ io ->getOutput (3 )),
123+ );
124+ }
125+
126+ public function testClearLogsWithoutForceButWithConfirmation (): void
127+ {
128+ $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
129+
130+ $ io = new MockInputOutput ();
131+ $ io ->setInputs (['y ' ]);
132+ CLI ::setInputOutput ($ io );
133+
134+ command ('logs:clear ' );
65135
66136 $ 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 );
137+ $ this ->assertSame ("Logs cleared. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ io ->getOutput (2 )));
138+ }
139+
140+ #[RequiresOperatingSystem('Darwin|Linux ' )]
141+ public function testClearLogsFailsOnChmodFailure (): void
142+ {
143+ $ path = WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " ;
144+ file_put_contents ($ path , 'Lorem ipsum ' );
145+
146+ // Attempt to make the file itself undeletable by setting the
147+ // immutable/uchg flag on supported platforms.
148+ $ immutableSet = false ;
149+ if (str_starts_with (PHP_OS , 'Darwin ' )) {
150+ @exec (sprintf ('chflags uchg %s ' , escapeshellarg ($ path )), $ output , $ rc );
151+ $ immutableSet = $ rc === 0 ;
152+ } else {
153+ // Try chattr on Linux
154+ @exec ('which chattr ' , $ whichOut , $ whichRc );
155+ if ($ whichRc === 0 ) {
156+ @exec (sprintf ('chattr +i %s ' , escapeshellarg ($ path )), $ output , $ rc );
157+ $ immutableSet = $ rc === 0 ;
158+ }
159+ }
160+
161+ if (! $ immutableSet ) {
162+ // Best-effort fallback: remove owner write bit. This may not
163+ // prevent unlink on all systems but gives some protection.
164+ chmod ($ path , 0400 );
165+ }
166+
167+ command ('logs:clear --force ' );
168+
169+ // Restore attributes so other tests are not affected.
170+ if ($ immutableSet ) {
171+ if (str_starts_with (PHP_OS , 'Darwin ' )) {
172+ @exec (sprintf ('chflags nouchg %s ' , escapeshellarg ($ path )));
173+ } else {
174+ @exec (sprintf ('chattr -i %s ' , escapeshellarg ($ path )));
175+ }
176+ } else {
177+ chmod ($ path , 0644 );
178+ }
179+
180+ $ this ->assertFileExists ($ path );
181+ $ this ->assertSame ("Error in deleting the logs files. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ this ->getStreamFilterBuffer ()));
69182 }
70183}
0 commit comments