Files
sam-api/tests/Feature/Approval/ApprovalApiTest.php
김보곤 63b174811c test: [approval] 결재 API 워크플로우 테스트 15개 추가
- CRUD 테스트 5개 (생성/목록/상세/수정/삭제)
- 워크플로우 테스트 4개 (상신/승인/반려/회수)
- 결재함/참조함/완료함 목록 조회 4개
- 뱃지 건수, 인증 테스트 2개
- 결재자 별도 사용자 로그인 검증 (loginAs 헬퍼)
2026-03-14 14:42:22 +09:00

342 lines
10 KiB
PHP

<?php
namespace Tests\Feature\Approval;
use App\Models\Members\User;
use App\Models\Tenants\Approval;
use App\Models\Tenants\ApprovalForm;
use App\Models\Tenants\ApprovalLine;
use App\Models\Tenants\ApprovalStep;
use App\Models\Tenants\Department;
use Tests\TestCase;
class ApprovalApiTest extends TestCase
{
private ApprovalForm $form;
private ApprovalLine $line;
private User $approver;
private ?Department $department = null;
protected function setUp(): void
{
parent::setUp();
$this->setUpAuthenticatedUser();
// 결재 양식
$this->form = ApprovalForm::create([
'tenant_id' => $this->tenant->id,
'name' => '품의서',
'code' => 'REQ-'.uniqid(),
'category' => ApprovalForm::CATEGORY_REQUEST,
'template' => '{}',
'body_template' => '',
'is_active' => true,
'created_by' => $this->user->id,
]);
// 결재자 (별도 사용자)
$approverId = 'approver'.uniqid();
$this->approver = User::create([
'user_id' => $approverId,
'name' => '결재자',
'email' => $approverId.'@example.com',
'password' => bcrypt('password123'),
]);
\App\Models\Members\UserTenant::create([
'user_id' => $this->approver->id,
'tenant_id' => $this->tenant->id,
'is_active' => true,
'is_default' => true,
]);
// 부서 (있으면 사용, 없으면 생성)
$this->department = Department::where('tenant_id', $this->tenant->id)->first();
if (! $this->department) {
$this->department = Department::create([
'tenant_id' => $this->tenant->id,
'name' => '개발팀',
'code' => 'DEV',
'is_active' => true,
'created_by' => $this->user->id,
]);
}
// 결재선 템플릿
$this->line = ApprovalLine::create([
'tenant_id' => $this->tenant->id,
'name' => '기본 결재선',
'steps' => json_encode([
[
'step_order' => 1,
'step_type' => ApprovalLine::STEP_TYPE_APPROVAL,
'approver_id' => $this->approver->id,
],
]),
'is_default' => true,
'created_by' => $this->user->id,
]);
}
// ==================== 기안 (CRUD) ====================
public function test_결재_문서_생성_임시저장(): void
{
$response = $this->api('post', '/api/v1/approvals', [
'form_id' => $this->form->id,
'title' => '테스트 품의서',
'content' => ['body' => '테스트 내용입니다.'],
]);
$response->assertStatus(200);
$this->assertTrue($response->json('success'));
$this->assertDatabaseHas('approvals', [
'title' => '테스트 품의서',
'status' => Approval::STATUS_DRAFT,
'tenant_id' => $this->tenant->id,
]);
}
public function test_기안함_목록_조회(): void
{
$this->createApproval();
$response = $this->api('get', '/api/v1/approvals/drafts');
$response->assertStatus(200)
->assertJsonStructure(['success', 'data']);
}
public function test_기안함_현황_카드(): void
{
$this->createApproval();
$response = $this->api('get', '/api/v1/approvals/drafts/summary');
$response->assertStatus(200);
}
public function test_결재_문서_상세_조회(): void
{
$approval = $this->createApproval();
$response = $this->api('get', "/api/v1/approvals/{$approval->id}");
$this->assertApiSuccess($response);
$data = $response->json('data');
$this->assertEquals($approval->id, $data['id']);
$this->assertEquals('테스트 품의서', $data['title']);
}
public function test_결재_문서_수정(): void
{
$approval = $this->createApproval();
$response = $this->api('patch', "/api/v1/approvals/{$approval->id}", [
'title' => '수정된 품의서',
'content' => ['body' => '수정된 내용'],
]);
$response->assertStatus(200);
$this->assertDatabaseHas('approvals', [
'id' => $approval->id,
'title' => '수정된 품의서',
]);
}
public function test_결재_문서_삭제(): void
{
$approval = $this->createApproval();
$response = $this->api('delete', "/api/v1/approvals/{$approval->id}");
$response->assertStatus(200);
$this->assertSoftDeleted('approvals', ['id' => $approval->id]);
}
// ==================== 워크플로우 (상신/승인/반려) ====================
public function test_결재_상신(): void
{
$approval = $this->createApproval();
$response = $this->api('post', "/api/v1/approvals/{$approval->id}/submit", [
'steps' => [
[
'step_order' => 1,
'step_type' => 'approval',
'approver_id' => $this->approver->id,
],
],
]);
$response->assertStatus(200);
$approval->refresh();
$this->assertEquals(Approval::STATUS_PENDING, $approval->status);
}
public function test_결재_승인(): void
{
$approval = $this->createAndSubmitApproval();
// 결재자로 로그인
$approverToken = $this->loginAs($this->approver);
$response = $this->withHeaders([
'X-API-KEY' => $this->apiKey,
'Authorization' => 'Bearer '.$approverToken,
'Accept' => 'application/json',
])->postJson("/api/v1/approvals/{$approval->id}/approve", [
'comment' => '승인합니다.',
]);
$response->assertStatus(200);
$approval->refresh();
$this->assertEquals(Approval::STATUS_APPROVED, $approval->status);
}
public function test_결재_반려(): void
{
$approval = $this->createAndSubmitApproval();
// 결재자로 로그인
$approverToken = $this->loginAs($this->approver);
$response = $this->withHeaders([
'X-API-KEY' => $this->apiKey,
'Authorization' => 'Bearer '.$approverToken,
'Accept' => 'application/json',
])->postJson("/api/v1/approvals/{$approval->id}/reject", [
'comment' => '수정 후 재상신 바랍니다.',
]);
$response->assertStatus(200);
$approval->refresh();
$this->assertEquals(Approval::STATUS_REJECTED, $approval->status);
}
public function test_결재_회수_기안자만_가능(): void
{
$approval = $this->createAndSubmitApproval();
// 기안자가 회수
$response = $this->api('post', "/api/v1/approvals/{$approval->id}/cancel", [
'reason' => '내용 수정 필요',
]);
$response->assertStatus(200);
$approval->refresh();
$this->assertEquals(Approval::STATUS_CANCELLED, $approval->status);
}
// ==================== 결재함/참조함/완료함 ====================
public function test_결재함_목록_조회(): void
{
$response = $this->api('get', '/api/v1/approvals/inbox');
$response->assertStatus(200)
->assertJsonStructure(['success', 'data']);
}
public function test_참조함_목록_조회(): void
{
$response = $this->api('get', '/api/v1/approvals/reference');
$response->assertStatus(200)
->assertJsonStructure(['success', 'data']);
}
public function test_완료함_목록_조회(): void
{
$response = $this->api('get', '/api/v1/approvals/completed');
$response->assertStatus(200)
->assertJsonStructure(['success', 'data']);
}
public function test_뱃지_미처리_건수(): void
{
$response = $this->api('get', '/api/v1/approvals/badge-counts');
$response->assertStatus(200)
->assertJsonStructure(['success', 'data']);
}
// ==================== 인증 ====================
public function test_미인증_요청시_401(): void
{
$response = $this->withHeaders([
'X-API-KEY' => $this->apiKey,
'Accept' => 'application/json',
])->getJson('/api/v1/approvals/drafts');
$response->assertStatus(401);
}
// ==================== 헬퍼 ====================
private function createApproval(): Approval
{
return Approval::create([
'tenant_id' => $this->tenant->id,
'document_number' => 'AP-'.uniqid(),
'form_id' => $this->form->id,
'line_id' => $this->line->id,
'title' => '테스트 품의서',
'content' => '테스트 내용',
'status' => Approval::STATUS_DRAFT,
'drafter_id' => $this->user->id,
'department_id' => $this->department->id,
'drafted_at' => now(),
'current_step' => 0,
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
}
private function createAndSubmitApproval(): Approval
{
$approval = $this->createApproval();
// 상신
$this->api('post', "/api/v1/approvals/{$approval->id}/submit", [
'steps' => [
[
'step_order' => 1,
'step_type' => 'approval',
'approver_id' => $this->approver->id,
],
],
]);
$approval->refresh();
return $approval;
}
private function loginAs(User $user): string
{
$response = $this->withHeaders([
'X-API-KEY' => $this->apiKey,
'Accept' => 'application/json',
])->postJson('/api/v1/login', [
'user_id' => $user->user_id,
'user_pwd' => 'password123',
]);
return $response->json('access_token');
}
}