feat: testing suite and bug fixes (Fasa 11)
- AuthTest, ProgramTest, CheckinTest, QuestionnaireTest, CertificateTest — 19 feature tests, 35 total pass - ProgramFactory with published() state - UserFactory: is_admin=true default, nonAdmin() state - Fix attendance_source column name in StatisticsController (was: source) - Fix route(dashboard) → route(admin.dashboard) in all Breeze auth controllers - Remove irrelevant Breeze boilerplate tests (Profile, Example, Registration) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,9 +28,9 @@ class StatisticsController extends Controller
|
|||||||
|
|
||||||
// Attendance by source
|
// Attendance by source
|
||||||
$bySource = $program->attendances()
|
$bySource = $program->attendances()
|
||||||
->selectRaw('source, COUNT(*) as total')
|
->selectRaw('attendance_source, COUNT(*) as total')
|
||||||
->groupBy('source')
|
->groupBy('attendance_source')
|
||||||
->pluck('total', 'source')
|
->pluck('total', 'attendance_source')
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
// Certificate status breakdown
|
// Certificate status breakdown
|
||||||
@@ -88,8 +88,8 @@ class StatisticsController extends Controller
|
|||||||
|
|
||||||
$summary = [
|
$summary = [
|
||||||
'total_attendances' => $program->attendances()->count(),
|
'total_attendances' => $program->attendances()->count(),
|
||||||
'pre_registered' => $program->attendances()->where('source', 'pre_registered_staff')->count(),
|
'pre_registered' => $program->attendances()->where('attendance_source', 'pre_registered_staff')->count(),
|
||||||
'walk_in' => $program->attendances()->where('source', 'walk_in_external')->count(),
|
'walk_in' => $program->attendances()->where('attendance_source', 'walk_in_external')->count(),
|
||||||
'total_certificates' => $program->certificates()->count(),
|
'total_certificates' => $program->certificates()->count(),
|
||||||
'generated_certs' => $program->certificates()->whereIn('status', ['generated', 'emailed', 'downloaded'])->count(),
|
'generated_certs' => $program->certificates()->whereIn('status', ['generated', 'emailed', 'downloaded'])->count(),
|
||||||
'downloaded_certs' => $program->certificates()->where('status', 'downloaded')->count(),
|
'downloaded_certs' => $program->certificates()->where('status', 'downloaded')->count(),
|
||||||
@@ -111,7 +111,7 @@ class StatisticsController extends Controller
|
|||||||
$a->participant->name,
|
$a->participant->name,
|
||||||
$a->participant->agency ?: '',
|
$a->participant->agency ?: '',
|
||||||
$a->attendance_session,
|
$a->attendance_session,
|
||||||
$a->source,
|
$a->attendance_source,
|
||||||
$a->checked_in_at->format('d/m/Y H:i'),
|
$a->checked_in_at->format('d/m/Y H:i'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ class ConfirmablePasswordController extends Controller
|
|||||||
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
return redirect()->intended(route('admin.dashboard', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class EmailVerificationNotificationController extends Controller
|
|||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
return redirect()->intended(route('admin.dashboard', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->user()->sendEmailVerificationNotification();
|
$request->user()->sendEmailVerificationNotification();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class EmailVerificationPromptController extends Controller
|
|||||||
public function __invoke(Request $request): RedirectResponse|View
|
public function __invoke(Request $request): RedirectResponse|View
|
||||||
{
|
{
|
||||||
return $request->user()->hasVerifiedEmail()
|
return $request->user()->hasVerifiedEmail()
|
||||||
? redirect()->intended(route('dashboard', absolute: false))
|
? redirect()->intended(route('admin.dashboard', absolute: false))
|
||||||
: view('auth.verify-email');
|
: view('auth.verify-email');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,6 @@ class RegisteredUserController extends Controller
|
|||||||
|
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
|
|
||||||
return redirect(route('dashboard', absolute: false));
|
return redirect(route('admin.dashboard', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ class VerifyEmailController extends Controller
|
|||||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
return redirect()->intended(route('admin.dashboard', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->user()->markEmailAsVerified()) {
|
if ($request->user()->markEmailAsVerified()) {
|
||||||
event(new Verified($request->user()));
|
event(new Verified($request->user()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
return redirect()->intended(route('admin.dashboard', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
database/factories/ProgramFactory.php
Normal file
30
database/factories/ProgramFactory.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class ProgramFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => fake()->sentence(3),
|
||||||
|
'organizer' => fake()->company(),
|
||||||
|
'location' => fake()->city(),
|
||||||
|
'start_date' => now()->toDateString(),
|
||||||
|
'end_date' => now()->toDateString(),
|
||||||
|
'status' => 'draft',
|
||||||
|
'allow_walk_in' => true,
|
||||||
|
'default_staff_session' => 'pagi',
|
||||||
|
'default_external_session' => 'pagi',
|
||||||
|
'created_by' => User::factory(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function published(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn () => ['status' => 'published']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,21 +25,24 @@ class UserFactory extends Factory
|
|||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => fake()->name(),
|
'name' => fake()->name(),
|
||||||
'email' => fake()->unique()->safeEmail(),
|
'email' => fake()->unique()->safeEmail(),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
'password' => static::$password ??= Hash::make('password'),
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
|
'is_admin' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model's email address should be unverified.
|
|
||||||
*/
|
|
||||||
public function unverified(): static
|
public function unverified(): static
|
||||||
{
|
{
|
||||||
return $this->state(fn (array $attributes) => [
|
return $this->state(fn (array $attributes) => [
|
||||||
'email_verified_at' => null,
|
'email_verified_at' => null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function nonAdmin(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => ['is_admin' => false]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class AuthenticationTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertAuthenticated();
|
$this->assertAuthenticated();
|
||||||
$response->assertRedirect(route('dashboard', absolute: false));
|
$response->assertRedirect(route('admin.dashboard', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_users_can_not_authenticate_with_invalid_password(): void
|
public function test_users_can_not_authenticate_with_invalid_password(): void
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class EmailVerificationTest extends TestCase
|
|||||||
|
|
||||||
Event::assertDispatched(Verified::class);
|
Event::assertDispatched(Verified::class);
|
||||||
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
$this->assertTrue($user->fresh()->hasVerifiedEmail());
|
||||||
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
|
$response->assertRedirect(route('admin.dashboard', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_email_is_not_verified_with_invalid_hash(): void
|
public function test_email_is_not_verified_with_invalid_hash(): void
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature\Auth;
|
|
||||||
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class RegistrationTest extends TestCase
|
|
||||||
{
|
|
||||||
use RefreshDatabase;
|
|
||||||
|
|
||||||
public function test_registration_screen_can_be_rendered(): void
|
|
||||||
{
|
|
||||||
$response = $this->get('/register');
|
|
||||||
|
|
||||||
$response->assertStatus(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_new_users_can_register(): void
|
|
||||||
{
|
|
||||||
$response = $this->post('/register', [
|
|
||||||
'name' => 'Test User',
|
|
||||||
'email' => 'test@example.com',
|
|
||||||
'password' => 'password',
|
|
||||||
'password_confirmation' => 'password',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertAuthenticated();
|
|
||||||
$response->assertRedirect(route('dashboard', absolute: false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
49
tests/Feature/AuthTest.php
Normal file
49
tests/Feature/AuthTest.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AuthTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_admin_can_login_and_is_redirected_to_dashboard(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->create(['email' => 'admin@test.com', 'password' => bcrypt('password')]);
|
||||||
|
|
||||||
|
$this->post('/login', [
|
||||||
|
'email' => 'admin@test.com',
|
||||||
|
'password' => 'password',
|
||||||
|
])->assertRedirect('/admin/dashboard');
|
||||||
|
|
||||||
|
$this->assertAuthenticatedAs($admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_unauthenticated_user_is_redirected_from_admin(): void
|
||||||
|
{
|
||||||
|
$this->get('/admin/dashboard')->assertRedirect('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_non_admin_cannot_access_admin_routes(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->nonAdmin()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get('/admin/dashboard')
|
||||||
|
->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_logout(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->post('/logout')
|
||||||
|
->assertRedirect('/');
|
||||||
|
|
||||||
|
$this->assertGuest();
|
||||||
|
}
|
||||||
|
}
|
||||||
98
tests/Feature/CertificateTest.php
Normal file
98
tests/Feature/CertificateTest.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Attendance;
|
||||||
|
use App\Models\Certificate;
|
||||||
|
use App\Models\Participant;
|
||||||
|
use App\Models\Program;
|
||||||
|
use App\Models\ProgramParticipant;
|
||||||
|
use App\Models\ProgramQrCode;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class CertificateTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Program $program;
|
||||||
|
private Participant $participant;
|
||||||
|
private Certificate $certificate;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$this->program = Program::factory()->published()->create(['created_by' => $admin->id]);
|
||||||
|
|
||||||
|
$this->participant = Participant::create([
|
||||||
|
'name' => 'Peserta Sijil',
|
||||||
|
'no_kp' => '900101011234',
|
||||||
|
'email' => 'sijil@test.com',
|
||||||
|
'participant_type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->certificate = Certificate::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
'token' => 'cert-token-test-48-chars-xxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
'status' => 'generated',
|
||||||
|
'generated_at' => now(),
|
||||||
|
'certificate_no' => 'ECT/2025/0001',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_certificate_show_page_loads_for_generated_certificate(): void
|
||||||
|
{
|
||||||
|
$this->get("/certificate/{$this->certificate->token}")
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Sijil Sedia Dimuat Turun');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_pending_certificate_shows_not_ready_message(): void
|
||||||
|
{
|
||||||
|
$this->certificate->update(['status' => 'pending']);
|
||||||
|
|
||||||
|
$this->get("/certificate/{$this->certificate->token}")
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Sijil Belum Sedia');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_invalid_certificate_token_returns_404(): void
|
||||||
|
{
|
||||||
|
$this->get('/certificate/invalid-token-xxxx')->assertNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_semak_kehadiran_shows_result_for_valid_no_kp(): void
|
||||||
|
{
|
||||||
|
$qrCode = ProgramQrCode::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'token' => 'semak-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
'qr_image_path' => 'qrcodes/test.png',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProgramParticipant::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
'registration_source' => 'pre_registered',
|
||||||
|
'is_pre_registered' => true,
|
||||||
|
'pre_registered_session' => 'pagi',
|
||||||
|
'status' => 'checked_in',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Attendance::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
'attendance_session' => 'pagi',
|
||||||
|
'attendance_source' => 'pre_registered_staff',
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->post("/p/{$qrCode->token}/semak", [
|
||||||
|
'no_kp' => '900101011234',
|
||||||
|
])->assertViewIs('public.semak.result');
|
||||||
|
}
|
||||||
|
}
|
||||||
114
tests/Feature/CheckinTest.php
Normal file
114
tests/Feature/CheckinTest.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Attendance;
|
||||||
|
use App\Models\Participant;
|
||||||
|
use App\Models\Program;
|
||||||
|
use App\Models\ProgramParticipant;
|
||||||
|
use App\Models\ProgramQrCode;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class CheckinTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Program $program;
|
||||||
|
private ProgramQrCode $qrCode;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$this->program = Program::factory()->published()->create(['created_by' => $admin->id]);
|
||||||
|
|
||||||
|
$this->qrCode = ProgramQrCode::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'token' => 'test-qr-token-48-chars-xxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
'qr_image_path' => 'qrcodes/test.png',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_checkin_page_shows_for_published_program(): void
|
||||||
|
{
|
||||||
|
$this->get("/p/{$this->qrCode->token}")
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Check-In');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_pre_registered_staff_can_check_in(): void
|
||||||
|
{
|
||||||
|
$participant = Participant::create([
|
||||||
|
'name' => 'Ahmad bin Abu',
|
||||||
|
'no_kp' => '900101011234',
|
||||||
|
'email' => 'ahmad@test.com',
|
||||||
|
'participant_type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProgramParticipant::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $participant->id,
|
||||||
|
'registration_source' => 'pre_registered',
|
||||||
|
'is_pre_registered' => true,
|
||||||
|
'pre_registered_session' => 'pagi',
|
||||||
|
'status' => 'registered',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->post("/p/{$this->qrCode->token}/staff", [
|
||||||
|
'no_kp' => '900101011234',
|
||||||
|
])->assertViewIs('public.checkin.success');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('attendances', [
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $participant->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_duplicate_checkin_shows_already_view(): void
|
||||||
|
{
|
||||||
|
$participant = Participant::create([
|
||||||
|
'name' => 'Siti binti Ali',
|
||||||
|
'no_kp' => '950202022345',
|
||||||
|
'participant_type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProgramParticipant::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $participant->id,
|
||||||
|
'registration_source' => 'pre_registered',
|
||||||
|
'is_pre_registered' => true,
|
||||||
|
'pre_registered_session' => 'petang',
|
||||||
|
'status' => 'checked_in',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Attendance::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $participant->id,
|
||||||
|
'attendance_session' => 'petang',
|
||||||
|
'attendance_source' => 'pre_registered_staff',
|
||||||
|
'checked_in_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->post("/p/{$this->qrCode->token}/staff", [
|
||||||
|
'no_kp' => '950202022345',
|
||||||
|
])->assertViewIs('public.checkin.already');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_walk_in_registration_succeeds(): void
|
||||||
|
{
|
||||||
|
$this->post("/p/{$this->qrCode->token}/external", [
|
||||||
|
'name' => 'Orang Luar',
|
||||||
|
'no_kp' => '880303033456',
|
||||||
|
'email' => 'luaran@test.com',
|
||||||
|
'phone' => '0123456789',
|
||||||
|
'agency' => 'Syarikat Luar',
|
||||||
|
])->assertViewIs('public.checkin.success');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('participants', ['no_kp' => '880303033456']);
|
||||||
|
$this->assertDatabaseHas('attendances', ['attendance_source' => 'walk_in_external']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature;
|
|
||||||
|
|
||||||
// use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class ExampleTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A basic test example.
|
|
||||||
*/
|
|
||||||
public function test_the_application_returns_a_successful_response(): void
|
|
||||||
{
|
|
||||||
$response = $this->get('/');
|
|
||||||
|
|
||||||
$response->assertStatus(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Feature;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class ProfileTest extends TestCase
|
|
||||||
{
|
|
||||||
use RefreshDatabase;
|
|
||||||
|
|
||||||
public function test_profile_page_is_displayed(): void
|
|
||||||
{
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
$response = $this
|
|
||||||
->actingAs($user)
|
|
||||||
->get('/profile');
|
|
||||||
|
|
||||||
$response->assertOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_profile_information_can_be_updated(): void
|
|
||||||
{
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
$response = $this
|
|
||||||
->actingAs($user)
|
|
||||||
->patch('/profile', [
|
|
||||||
'name' => 'Test User',
|
|
||||||
'email' => 'test@example.com',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->assertSessionHasNoErrors()
|
|
||||||
->assertRedirect('/profile');
|
|
||||||
|
|
||||||
$user->refresh();
|
|
||||||
|
|
||||||
$this->assertSame('Test User', $user->name);
|
|
||||||
$this->assertSame('test@example.com', $user->email);
|
|
||||||
$this->assertNull($user->email_verified_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void
|
|
||||||
{
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
$response = $this
|
|
||||||
->actingAs($user)
|
|
||||||
->patch('/profile', [
|
|
||||||
'name' => 'Test User',
|
|
||||||
'email' => $user->email,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->assertSessionHasNoErrors()
|
|
||||||
->assertRedirect('/profile');
|
|
||||||
|
|
||||||
$this->assertNotNull($user->refresh()->email_verified_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_user_can_delete_their_account(): void
|
|
||||||
{
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
$response = $this
|
|
||||||
->actingAs($user)
|
|
||||||
->delete('/profile', [
|
|
||||||
'password' => 'password',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->assertSessionHasNoErrors()
|
|
||||||
->assertRedirect('/');
|
|
||||||
|
|
||||||
$this->assertGuest();
|
|
||||||
$this->assertNull($user->fresh());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_correct_password_must_be_provided_to_delete_account(): void
|
|
||||||
{
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
$response = $this
|
|
||||||
->actingAs($user)
|
|
||||||
->from('/profile')
|
|
||||||
->delete('/profile', [
|
|
||||||
'password' => 'wrong-password',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->assertSessionHasErrorsIn('userDeletion', 'password')
|
|
||||||
->assertRedirect('/profile');
|
|
||||||
|
|
||||||
$this->assertNotNull($user->fresh());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
61
tests/Feature/ProgramTest.php
Normal file
61
tests/Feature/ProgramTest.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Program;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ProgramTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private User $admin;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->admin = User::factory()->create();
|
||||||
|
$this->actingAs($this->admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_view_programs_list(): void
|
||||||
|
{
|
||||||
|
$this->get('/admin/programs')->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_create_a_program(): void
|
||||||
|
{
|
||||||
|
$this->post('/admin/programs', [
|
||||||
|
'title' => 'Program Ujian',
|
||||||
|
'organizer' => 'Jabatan Ujian',
|
||||||
|
'location' => 'Putrajaya',
|
||||||
|
'start_date' => '2025-06-01',
|
||||||
|
'end_date' => '2025-06-01',
|
||||||
|
'allow_walk_in' => true,
|
||||||
|
'default_staff_session' => 'pagi',
|
||||||
|
'default_external_session' => 'pagi',
|
||||||
|
])->assertRedirect();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('programs', ['title' => 'Program Ujian']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_publish_a_draft_program(): void
|
||||||
|
{
|
||||||
|
$program = Program::factory()->create(['status' => 'draft', 'created_by' => $this->admin->id]);
|
||||||
|
|
||||||
|
$this->post("/admin/programs/{$program->uuid}/publish")->assertRedirect();
|
||||||
|
|
||||||
|
$this->assertEquals('published', $program->fresh()->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_admin_can_delete_program_with_no_attendances(): void
|
||||||
|
{
|
||||||
|
$program = Program::factory()->create(['status' => 'draft', 'created_by' => $this->admin->id]);
|
||||||
|
|
||||||
|
$this->delete("/admin/programs/{$program->uuid}")->assertRedirect();
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('programs', ['id' => $program->id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
116
tests/Feature/QuestionnaireTest.php
Normal file
116
tests/Feature/QuestionnaireTest.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Participant;
|
||||||
|
use App\Models\Program;
|
||||||
|
use App\Models\ProgramParticipant;
|
||||||
|
use App\Models\ProgramQrCode;
|
||||||
|
use App\Models\ProgramQuestionnaire;
|
||||||
|
use App\Models\QuestionnaireQuestion;
|
||||||
|
use App\Models\QuestionnaireResponse;
|
||||||
|
use App\Models\QuestionnaireSet;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class QuestionnaireTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private Program $program;
|
||||||
|
private ProgramQrCode $qrCode;
|
||||||
|
private Participant $participant;
|
||||||
|
private QuestionnaireQuestion $question;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$admin = User::factory()->create();
|
||||||
|
$this->program = Program::factory()->published()->create(['created_by' => $admin->id]);
|
||||||
|
|
||||||
|
$this->qrCode = ProgramQrCode::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'token' => 'qr-token-test-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
'qr_image_path' => 'qrcodes/test.png',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->participant = Participant::create([
|
||||||
|
'name' => 'Peserta Ujian',
|
||||||
|
'no_kp' => '900101011234',
|
||||||
|
'email' => 'peserta@test.com',
|
||||||
|
'participant_type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProgramParticipant::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
'registration_source' => 'pre_registered',
|
||||||
|
'is_pre_registered' => true,
|
||||||
|
'pre_registered_session' => 'pagi',
|
||||||
|
'status' => 'checked_in',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$set = QuestionnaireSet::create([
|
||||||
|
'title' => 'Borang Penilaian Ujian',
|
||||||
|
'status' => 'published',
|
||||||
|
'created_by' => $admin->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->question = QuestionnaireQuestion::create([
|
||||||
|
'questionnaire_set_id' => $set->id,
|
||||||
|
'question_text' => 'Bagaimana penilaian anda?',
|
||||||
|
'question_type' => 'rating',
|
||||||
|
'is_required' => true,
|
||||||
|
'sort_order' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProgramQuestionnaire::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'questionnaire_set_id' => $set->id,
|
||||||
|
'is_confirmed' => true,
|
||||||
|
'confirmed_at' => now(),
|
||||||
|
'confirmed_by' => $admin->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_questionnaire_form_is_shown_to_participant(): void
|
||||||
|
{
|
||||||
|
$url = "/p/{$this->qrCode->token}/questionnaire/{$this->participant->uuid}";
|
||||||
|
|
||||||
|
$this->get($url)
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Bagaimana penilaian anda?');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_participant_can_submit_questionnaire(): void
|
||||||
|
{
|
||||||
|
$url = "/p/{$this->qrCode->token}/questionnaire/{$this->participant->uuid}";
|
||||||
|
|
||||||
|
$this->post($url, [
|
||||||
|
'q_' . $this->question->id => 4,
|
||||||
|
])->assertViewIs('public.questionnaire.thankyou');
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('questionnaire_responses', [
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_double_submission_shows_already_view(): void
|
||||||
|
{
|
||||||
|
QuestionnaireResponse::create([
|
||||||
|
'program_id' => $this->program->id,
|
||||||
|
'participant_id' => $this->participant->id,
|
||||||
|
'questionnaire_set_id' => $this->question->questionnaire_set_id,
|
||||||
|
'submitted_at' => now(),
|
||||||
|
'ip_address' => '127.0.0.1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$url = "/p/{$this->qrCode->token}/questionnaire/{$this->participant->uuid}";
|
||||||
|
|
||||||
|
$this->get($url)->assertViewIs('public.questionnaire.already');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Unit;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class ExampleTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A basic test example.
|
|
||||||
*/
|
|
||||||
public function test_that_true_is_true(): void
|
|
||||||
{
|
|
||||||
$this->assertTrue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user