diff --git a/tests/Feature/Admin/Finance/PayrollTest.php b/tests/Feature/Admin/Finance/PayrollTest.php new file mode 100644 index 0000000..b8e2d7e --- /dev/null +++ b/tests/Feature/Admin/Finance/PayrollTest.php @@ -0,0 +1,175 @@ +assertRedirect(route('login')); + }); + + it('returns 403 when user has no permission to view payrolls', function () { + actingAs(createUnauthorizedUser()) + ->get(route('payroll.index')) + ->assertStatus(403); + }); +}); + +describe('Payroll Module - Authorized Actions', function () { + beforeEach(function () { + $user = createAuthorizedUser([ + 'View:Payroll', + 'Generate:Payroll', + 'Edit:PayrollAdjustment', + 'Pay:Payroll', + 'Delete:Payroll', + 'DeleteAny:Payroll', + ]); + actingAs($user); + }); + + it('can access payroll index page', function () { + get(route('payroll.index')) + ->assertOk() + ->assertInertia(fn ($page) => $page + ->component('admin/finance/payroll/index') + ->has('payrolls') + ->has('currentMonthPayrolls') + ); + }); + + it('can generate payroll', function () { + // Mocking artisan call is tricky if we want to check side effects, + // but here we check the response and session. + postJson(route('payroll.generate')) + ->assertRedirect() + ->assertSessionHas('success'); + }); + + it('can update payroll adjustments', function () { + $payroll = Payroll::factory()->create([ + 'base_salary' => 3000000, + 'bonus' => 0, + 'deduction' => 0, + 'total_salary' => 3000000, + ]); + + $data = [ + 'adjustments' => [ + [ + 'type' => SalaryAdjustmentType::BONUS->value, + 'amount' => 500000, + 'description' => 'Bonus Kerajinan', + ], + [ + 'type' => SalaryAdjustmentType::DEDUCTION->value, + 'amount' => 100000, + 'description' => 'Potongan Terlambat', + ], + ], + ]; + + patchJson(route('payroll.update', $payroll), $data) + ->assertRedirect() + ->assertSessionHas('success'); + + assertDatabaseHas('payrolls', [ + 'id' => $payroll->id, + 'bonus' => 500000, + 'deduction' => 100000, + 'total_salary' => 3400000, // 3M + 500k - 100k + ]); + + assertDatabaseHas('payroll_adjustments', [ + 'payroll_id' => $payroll->id, + 'description' => 'Bonus Kerajinan', + ]); + }); + + it('can toggle paid status', function () { + $payroll = Payroll::factory()->create(['is_paid' => false, 'paid_at' => null]); + + patchJson(route('payroll.togglePaid', $payroll)) + ->assertRedirect() + ->assertSessionHas('success'); + + $payroll->refresh(); + expect($payroll->is_paid)->toBeTrue(); + expect($payroll->paid_at)->not->toBeNull(); + + patchJson(route('payroll.togglePaid', $payroll)) + ->assertRedirect(); + + $payroll->refresh(); + expect($payroll->is_paid)->toBeFalse(); + expect($payroll->paid_at)->toBeNull(); + }); + + it('can delete a payroll', function () { + $payroll = Payroll::factory()->create(); + + deleteJson(route('payroll.destroy', $payroll)) + ->assertRedirect() + ->assertSessionHas('success'); + + assertSoftDeleted('payrolls', ['id' => $payroll->id]); + }); + + it('can delete payrolls in bulk', function () { + $payrolls = Payroll::factory()->count(3)->create(); + $ids = $payrolls->pluck('id')->toArray(); + + deleteJson(route('payroll.bulkDestroy'), ['ids' => $ids]) + ->assertRedirect() + ->assertSessionHas('success'); + + foreach ($ids as $id) { + assertSoftDeleted('payrolls', ['id' => $id]); + } + }); +}); + +describe('Payroll Module - Unauthorized Actions', function () { + beforeEach(function () { + actingAs(createUnauthorizedUser()); + }); + + it('cannot generate payroll without permission', function () { + postJson(route('payroll.generate')) + ->assertStatus(403); + }); + + it('cannot update payroll adjustment without permission', function () { + $payroll = Payroll::factory()->create(); + patchJson(route('payroll.update', $payroll), ['adjustments' => []]) + ->assertStatus(403); + }); + + it('cannot toggle paid status without permission', function () { + $payroll = Payroll::factory()->create(); + patchJson(route('payroll.togglePaid', $payroll)) + ->assertStatus(403); + }); + + it('cannot delete a payroll without permission', function () { + $payroll = Payroll::factory()->create(); + deleteJson(route('payroll.destroy', $payroll)) + ->assertStatus(403); + }); +});