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,142 @@ 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+ }
5883
59- // test dir is now populated with logs
60- $ this ->createDummyLogFiles ();
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 ' );
93+
94+ $ 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+ }
104+
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+ {
61128 $ this ->assertFileExists (WRITEPATH . 'logs ' . DIRECTORY_SEPARATOR . "log- {$ this ->date }.log " );
62129
63- command ('logs:clear -force ' );
64- $ result = $ this ->getStreamFilterBuffer ();
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 with sudo (for containerized environments)
154+ @exec ('which chattr ' , $ whichOut , $ whichRc );
155+
156+ if ($ whichRc === 0 ) {
157+ @exec (sprintf ('sudo chattr +i %s ' , escapeshellarg ($ path )), $ output , $ rc );
158+ $ immutableSet = $ rc === 0 ;
159+ }
160+ }
161+
162+ if (! $ immutableSet ) {
163+ $ this ->markTestSkipped ('Cannot set file immutability in this environment ' );
164+ }
165+
166+ command ('logs:clear --force ' );
167+
168+ // Restore attributes so other tests are not affected.
169+ if (str_starts_with (PHP_OS , 'Darwin ' )) {
170+ @exec (sprintf ('chflags nouchg %s ' , escapeshellarg ($ path )));
171+ } else {
172+ @exec (sprintf ('sudo chattr -i %s ' , escapeshellarg ($ path )));
173+ }
174+
175+ $ this ->assertFileExists ($ path );
176+ $ this ->assertSame ("Error in deleting the logs files. \n" , preg_replace ('/\e\[[^m]+m/ ' , '' , $ this ->getStreamFilterBuffer ()));
69177 }
70178}
0 commit comments