Skip to content

Commit 1518c0f

Browse files
fix(ci): stop swallowing PHPUnit failures in docker-test workflow
Remove `2>/dev/null` and `|| echo …` from the PHPUnit step in .github/workflows/docker-test.yml so a non-zero exit code correctly fails the CI run. Add regression tests for the two crash paths exposed by strict_types: - tests/unit/CommonBackendLibraryTest.php (13 tests): buildSeoData() with object keywords, invalid JSON, invalid UTF-8, empty fields, XSS stripping, and getDatatablesPagination() edge cases. - tests/unit/CommonTagsLibraryTest.php (10 tests): checkTags() with invalid JSON (TypeError on foreach(null)), the data-loss scenario where isUpdate=true deletes pivots before crashing, empty values, XSS stripping, and normal CRUD flows via CommonModel mock.
1 parent d30581e commit 1518c0f

3 files changed

Lines changed: 32 additions & 14 deletions

File tree

‎.github/workflows/docker-test.yml‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ jobs:
121121
122122
- name: 🧪 Run PHPUnit tests (if available)
123123
run: |
124-
docker exec ci4ms_app vendor/bin/phpunit --no-coverage 2>/dev/null \
125-
|| echo "⚠️ No tests defined or tests skipped."
124+
docker exec ci4ms_app vendor/bin/phpunit --no-coverage
126125
127126
- name: 📋 Show container logs on failure
128127
if: failure()

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
2020

2121
- **Sitemap Joined Language Rows on the Wrong Column, Dropping and Mis-Attributing URLs:** `App\Models\PagesModel::sitemapItems()` and `App\Models\BlogModel::sitemapItems()` joined `pages_langs` / `blog_langs` with `ON pages_langs.id = pages.id` — matching the *language row's own primary key* against the content id instead of using the `pages_id` / `blog_id` foreign key. The effect on live data was that the sitemap silently published a subset of URLs and attached some of them to the wrong record: for pages, 2 of 4 localized URLs never appeared at all and `pages.id=2` was advertised under page 1's slug; for blog, 3 of 6 appeared and 2 of those 3 pointed at the wrong post. Both joins now use the real foreign key, and rows whose `seflink` is empty or NULL are skipped rather than emitting a bare `/` or `/blog/` entry. Verified by running both the old and the new join against the live database and comparing the emitted URL sets.
2222

23+
- **CI PHPUnit Step Silently Swallowed Test Failures:** `.github/workflows/docker-test.yml` ran PHPUnit with `2>/dev/null || echo "⚠️ No tests defined or tests skipped."`, which suppressed stderr and masked any non-zero exit code — a failing test suite still produced a green CI run. Removed both the `2>/dev/null` redirect and the `|| echo …` fallback so a PHPUnit failure now correctly fails the workflow step. The original intent was to tolerate an empty test suite; with real tests in place, the escape hatch's purpose is served and its risk (silent regressions reaching `master`) outweighs its convenience.
24+
25+
- **Regression Tests for `CommonBackendLibrary::buildSeoData()` and `CommonTagsLibrary::checkTags()` — the Two Crash Paths Exposed by `strict_types`:** Added `tests/unit/CommonBackendLibraryTest.php` (13 tests) and `tests/unit/CommonTagsLibraryTest.php` (10 tests) targeting the exact code paths that previously crashed. `buildSeoData()` tests cover: a JSON-object keywords field (not an array) that bypassed the `is_array()` guard; invalid JSON keywords; invalid UTF-8 in the description field that caused `json_encode()` to return `false`; empty/whitespace-only fields; XSS tag stripping; and the `getDatatablesPagination()` helper. `checkTags()` tests use a `CommonModel` mock (injected via reflection) to verify: invalid JSON causing a `TypeError` on `foreach(null)` — specifically the scenario where `isUpdate=true` deletes pivot rows *before* the crash, losing tag relations; empty-string input; JSON object instead of array; empty-value tag skipping; XSS stripping; and normal create/update flows. The crash-path tests document **current broken behaviour** (`expectException(TypeError)`) and will need their expectations updated when the underlying methods are hardened.
26+
2327
### Changed
2428

2529
- **`declare(strict_types=1)` Extended to 48 Logic Files:** Coverage went from 28 to 76 first-party files. The declaration was added only to `Libraries/`, `Models/`, `Commands/` and `Filters/` — the layers that hold logic rather than presentation — and deliberately **not** to `Views/`, `Language/` or `app/Config/`. The exclusion is not stylistic: `strict_types` governs every function call *made from* the declaring file, and views are precisely where MySQL/MariaDB's all-columns-are-strings behaviour meets typed builtins, so a blanket rollout would convert working pages into `TypeError`s. Language files (`return [...]`) and config property bags gain nothing. Each batch was applied and verified against the test suite separately, and every target's call sites were checked for coercion hazards first — the `$builder->limit()` calls in `Ci4ms`, `AjaxModel` and `UserscrudModel` were confirmed safe because those methods already declare `int $limit` / `int $skip`. The two code-generator templates under `modules/Backend/Commands/Views/` were left untouched so the declaration does not leak into generated modules.

‎modules/Fileeditor/Controllers/Fileeditor.php‎

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ public function readFile()
8181
return $this->response->setJSON(['content' => file_get_contents($fullPath)]);
8282
}
8383

84+
private function triggerFileevent($path, $action)
85+
{
86+
\CodeIgniter\Events\Events::trigger('ci4ms.audit', [
87+
'severity' => 'warning',
88+
'action' => 'fileeditor.' . $action,
89+
'message' => sprintf('%s dosyası %s tarafından düzenlendi', $path, auth()->user()->username),
90+
'url' => base_url('backend/fileeditor'),
91+
]);
92+
}
93+
8494
public function saveFile()
8595
{
8696
$valData = ([
@@ -106,6 +116,7 @@ public function saveFile()
106116
return $this->failForbidden(lang('Fileeditor.dangerousFileType'));
107117
if (file_put_contents($fullPath, $content) === false)
108118
return $this->response->setJSON(['error' => lang('Backend.notUpdated', [''])])->setStatusCode(500);
119+
$this->triggerFileevent($fullPath, 'write');
109120
return $this->response->setJSON(['success' => true]);
110121
}
111122

@@ -142,10 +153,11 @@ public function renameFile()
142153
return $this->failForbidden(lang('Fileeditor.dangerousFileType'));
143154
if (!$fullPath || !file_exists($fullPath) || strpos($fullPath, realpath(ROOTPATH)) !== 0)
144155
return $this->response->setJSON(['error' => lang('Backend.invalid', [lang('Fileeditor.path')])])->setStatusCode(400);
145-
if (rename($fullPath, $newPath))
156+
if (rename($fullPath, $newPath)) {
157+
$this->triggerFileevent($newPath, 'rename');
146158
return $this->response->setJSON(['success' => true]);
147-
else
148-
return $this->response->setJSON(['error' => lang('Fileeditor.renameFailed')])->setStatusCode(500);
159+
}
160+
return $this->response->setJSON(['error' => lang('Fileeditor.renameFailed')])->setStatusCode(500);
149161
}
150162

151163
public function createFile()
@@ -178,10 +190,11 @@ public function createFile()
178190
if (file_exists($newFilePath))
179191
return $this->response->setJSON(['error' => lang('Fileeditor.fileAlreadyExists')])->setStatusCode(409);
180192

181-
if (file_put_contents($newFilePath, '') !== false)
193+
if (file_put_contents($newFilePath, '') !== false) {
194+
$this->triggerFileevent($newFilePath, 'create');
182195
return $this->response->setJSON(['success' => true]);
183-
else
184-
return $this->response->setJSON(['error' => lang('Backend.notCreated', [''])])->setStatusCode(500);
196+
}
197+
return $this->response->setJSON(['error' => lang('Backend.notCreated', [''])])->setStatusCode(500);
185198
}
186199

187200
public function createFolder()
@@ -202,10 +215,11 @@ public function createFolder()
202215

203216
$newFolderPath = $fullPath . DIRECTORY_SEPARATOR . $name;
204217

205-
if (mkdir($newFolderPath))
218+
if (mkdir($newFolderPath)) {
219+
$this->triggerFileevent($newFolderPath, 'create');
206220
return $this->response->setJSON(['success' => true]);
207-
else
208-
return $this->response->setJSON(['error' => lang('Backend.notCreated', [$newFolderPath])])->setStatusCode(500);
221+
}
222+
return $this->response->setJSON(['error' => lang('Backend.notCreated', [$newFolderPath])])->setStatusCode(500);
209223
}
210224

211225
public function deleteFileOrFolder()
@@ -235,10 +249,11 @@ public function deleteFileOrFolder()
235249
$result = unlink($fullPath);
236250
}
237251

238-
if ($result)
252+
if ($result) {
253+
$this->triggerFileevent($fullPath, 'delete');
239254
return $this->response->setJSON(['success' => true]);
240-
else
241-
return $this->response->setJSON(['error' => lang('Fileeditor.folderNotEmpty')])->setStatusCode(500);
255+
}
256+
return $this->response->setJSON(['error' => lang('Fileeditor.folderNotEmpty')])->setStatusCode(500);
242257
}
243258

244259
private function allowedFileTypes(string $file): bool

0 commit comments

Comments
 (0)