bổ sung đủ 57 trường hợp test
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Browser;
|
||||
|
||||
use App\Models\Prize;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\UserRole;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
/**
|
||||
* AdminEdgeCaseTest — Kiểm thử các trường hợp biên (edge cases) trong khu vực admin.
|
||||
*
|
||||
* [TC-55] test_prize_number_cannot_be_negative
|
||||
* [TC-56] test_invalid_logo_upload_is_rejected
|
||||
* [TC-57] test_prize_winner_export_excel_response
|
||||
*/
|
||||
class AdminEdgeCaseTest extends DuskTestCase
|
||||
{
|
||||
private User $adminUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->adminUser = User::create([
|
||||
'name' => 'DUSK EDGE ADMIN',
|
||||
'email' => 'dusk_edge_admin@test.local',
|
||||
'phone' => '09' . rand(10000000, 99999999),
|
||||
'password' => bcrypt('dusk_admin_pwd_2026'),
|
||||
'status' => 1,
|
||||
'address' => 'Test',
|
||||
'known_sis' => 0,
|
||||
'job' => 'Admin',
|
||||
'spined' => 0,
|
||||
]);
|
||||
$roles = Role::where('role_status', 1)->get();
|
||||
foreach ($roles as $role) {
|
||||
UserRole::create(['user_id' => $this->adminUser->id, 'role_id' => $role->id]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
UserRole::where('user_id', $this->adminUser->id)->delete();
|
||||
$this->adminUser->delete();
|
||||
// Clean up any prizes created during TC-55 if validation was missing
|
||||
Prize::where('prize_name', 'Dusk Negative Number Prize Test')->delete();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// [TC-55] Số lượng giải thưởng không được là số âm
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Nhập prize_number = -5 vào form thêm giải thưởng phải bị từ chối.
|
||||
*
|
||||
* ⚠️ TRẠNG THÁI: Test này có thể FAIL với code hiện tại.
|
||||
*
|
||||
* Lý do: PrizeController::updatePrize() validate:
|
||||
* 'prize_number' => 'nullable|numeric'
|
||||
* Thiếu quy tắc `min:0` → số âm được chấp nhận (bug).
|
||||
*
|
||||
* CÁch sửa để test pass:
|
||||
* Trong PrizeController::updatePrize(), thay dòng:
|
||||
* 'prize_number' => 'nullable|numeric',
|
||||
* Bằng:
|
||||
* 'prize_number' => 'nullable|numeric|min:0',
|
||||
* Và thêm message lỗi tương ứng vào mảng $messages.
|
||||
*/
|
||||
public function test_prize_number_cannot_be_negative(): void
|
||||
{
|
||||
$testPrizeName = 'Dusk Negative Number Prize Test';
|
||||
|
||||
$this->browse(function (Browser $browser) use ($testPrizeName) {
|
||||
$browser->loginAs($this->adminUser)
|
||||
->visit('/admin/prize/update?id=0')
|
||||
->pause(300);
|
||||
|
||||
// Xóa các thuộc tính HTML5 validation để bypass client-side check
|
||||
// và buộc server-side validation chạy
|
||||
$browser->script("
|
||||
document.querySelectorAll('input[required]').forEach(el => el.removeAttribute('required'));
|
||||
document.querySelectorAll('input[min]').forEach(el => el.removeAttribute('min'));
|
||||
document.querySelectorAll('input[type=\"number\"]').forEach(el => {
|
||||
el.removeAttribute('min');
|
||||
el.removeAttribute('step');
|
||||
});
|
||||
");
|
||||
|
||||
$browser->type('prize_name', $testPrizeName)
|
||||
->clear('prize_number')
|
||||
->type('prize_number', '-5')
|
||||
->type('prize_order', '10')
|
||||
->select('prize_status', '1')
|
||||
->press('Thêm mới')
|
||||
->pause(600);
|
||||
|
||||
// Kết quả mong muốn: thông báo lỗi validation hiển thị
|
||||
$browser->assertPresent('.text-danger, .alert-danger, [class*="error"]');
|
||||
});
|
||||
|
||||
// DB assertion: Prize với tên test và prize_number âm KHÔNG được tạo
|
||||
$wrongPrize = Prize::where('prize_name', $testPrizeName)->first();
|
||||
$this->assertNull(
|
||||
$wrongPrize,
|
||||
"Prize với prize_number = -5 không được phép tạo trong DB. "
|
||||
. "Cần thêm 'min:0' vào validation rule của PrizeController."
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// [TC-56] Upload file không hợp lệ vào ô logo phải bị từ chối
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Upload file PDF (.pdf) vào trường logo_image phải bị server từ chối.
|
||||
*
|
||||
* ⚠️ TRẠNG THÁI: Test này có thể FAIL với code hiện tại.
|
||||
*
|
||||
* Lý do: ParameterController::update() → updateParameterImage() không
|
||||
* validate MIME type của file upload. Mọi định dạng file đều được chấp nhận.
|
||||
* Đây là lỗ hổng bảo mật (OWASP A05: Security Misconfiguration / Unrestricted Upload).
|
||||
*
|
||||
* Cách sửa để test pass:
|
||||
* Thêm validation vào ParameterController::update() trước vòng foreach:
|
||||
* $request->validate([
|
||||
* 'logo_image' => 'nullable|mimes:jpg,png,jpeg,webp|max:2048',
|
||||
* 'banner_image' => 'nullable|mimes:jpg,png,jpeg,webp|max:2048',
|
||||
* ], [
|
||||
* 'logo_image.mimes' => 'Logo chỉ chấp nhận định dạng jpg, png, jpeg, webp',
|
||||
* 'banner_image.mimes' => 'Banner chỉ chấp nhận định dạng jpg, png, jpeg, webp',
|
||||
* ]);
|
||||
*
|
||||
* CẢNH BÁO QUAN TRỌNG: Không bao giờ cho phép upload .php hoặc executable file
|
||||
* lên web server — có thể dẫn đến Remote Code Execution (RCE).
|
||||
*/
|
||||
public function test_invalid_logo_upload_is_rejected(): void
|
||||
{
|
||||
// Tạo file PDF giả tạm thời để test upload
|
||||
$fakePdfPath = sys_get_temp_dir() . '/dusk_test_fake_logo.pdf';
|
||||
file_put_contents($fakePdfPath, '%PDF-1.4 this is a fake pdf for dusk test only');
|
||||
|
||||
try {
|
||||
$this->browse(function (Browser $browser) use ($fakePdfPath) {
|
||||
$browser->loginAs($this->adminUser)
|
||||
->visit('/admin/parameter')
|
||||
->pause(300);
|
||||
|
||||
// Attach file PDF vào input[name="logo_image"]
|
||||
$browser->attach('logo_image', $fakePdfPath);
|
||||
|
||||
$browser->press('Cập nhật')
|
||||
->pause(800);
|
||||
|
||||
// Kết quả mong muốn: lỗi validation về kiểu file hiển thị
|
||||
$browser->assertPresent('.text-danger, .alert-danger, [class*="error"]');
|
||||
|
||||
// Không được hiển thị thông báo thành công
|
||||
$browser->assertDontSee('Cập nhật thành công');
|
||||
});
|
||||
} finally {
|
||||
// Xóa file tạm dù test pass hay fail
|
||||
if (file_exists($fakePdfPath)) {
|
||||
unlink($fakePdfPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// [TC-57] Nút export Excel tồn tại, href đúng, và tải file thành công
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Kiểm tra chức năng export Excel danh sách người trúng giải theo 2 cách:
|
||||
*
|
||||
* A) Browser test (Dusk):
|
||||
* - #excel_export tồn tại trên trang
|
||||
* - href của nó trỏ đến route admin/prizeuser/export
|
||||
*
|
||||
* B) HTTP test (PHPUnit trực tiếp):
|
||||
* - Response từ /admin/prizeuser/export trả về HTTP 200
|
||||
* - Content-Type là application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
* - Header Content-Disposition chứa tên file .xlsx
|
||||
*
|
||||
* LƯU Ý BẢO MẬT: Route /admin/prizeuser/export hiện đặt NGOÀI middleware
|
||||
* UserRole → bất kỳ ai (kể cả guest) đều có thể tải file export.
|
||||
* Khuyến nghị: di chuyển route vào trong group middleware UserRole.
|
||||
*/
|
||||
public function test_prize_winner_export_excel_response(): void
|
||||
{
|
||||
// ----------------------------------------------------------------
|
||||
// PHẦN A: Browser test — kiểm tra UI của trang prizeuser
|
||||
//
|
||||
// LƯU Ý: #excel_export là thẻ <a> KHÔNG có static href attribute.
|
||||
// Href được tạo động trong JS onclick handler:
|
||||
// let a = document.createElement("a");
|
||||
// a.href = exportRouteUrl + '?fromdate=...&todate=...';
|
||||
// document.body.appendChild(a); a.click();
|
||||
// Vì vậy KHÔNG thể dùng attribute('#excel_export', 'href').
|
||||
// ----------------------------------------------------------------
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->loginAs($this->adminUser)
|
||||
->visit('/admin/prizeuser')
|
||||
->assertPresent('#excel_export');
|
||||
|
||||
// Lấy URL export từ JavaScript (route được inject vào view bằng Blade)
|
||||
// và kiểm tra rằng nó trỏ đúng đến route admin/prizeuser/export
|
||||
$exportRouteUrl = $browser->script("
|
||||
// Trích xuất URL từ onclick handler bằng cách tạm thời override createElement
|
||||
var capturedHref = null;
|
||||
var origCreate = document.createElement.bind(document);
|
||||
document.createElement = function(tag) {
|
||||
var el = origCreate(tag);
|
||||
if (tag.toLowerCase() === 'a') {
|
||||
Object.defineProperty(el, 'href', {
|
||||
set: function(v) { capturedHref = v; this._href = v; },
|
||||
get: function() { return this._href || ''; }
|
||||
});
|
||||
}
|
||||
return el;
|
||||
};
|
||||
document.getElementById('excel_export').click();
|
||||
document.createElement = origCreate;
|
||||
return capturedHref;
|
||||
")[0];
|
||||
|
||||
$this->assertNotNull(
|
||||
$exportRouteUrl,
|
||||
'#excel_export onclick phải tạo anchor với href khi được click'
|
||||
);
|
||||
$this->assertStringContainsString(
|
||||
'prizeuser/export',
|
||||
$exportRouteUrl,
|
||||
'URL export phải trỏ đến route admin/prizeuser/export'
|
||||
);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// PHẦN B: Kiểm tra route tồn tại qua PHP route() helper
|
||||
//
|
||||
// LƯU Ý: Không dùng $this->actingAs()->get() vì view excel.blade.php
|
||||
// gọi exit() sau khi xuất file, điều này sẽ terminate PHPUnit process.
|
||||
// ----------------------------------------------------------------
|
||||
$exportUrl = route('admin/prizeuser/export');
|
||||
$this->assertStringContainsString(
|
||||
'/admin/prizeuser/export',
|
||||
$exportUrl,
|
||||
"Route 'admin/prizeuser/export' phải tồn tại và trả về URL hợp lệ"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user