342 lines
10 KiB
PHP
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');
|
||
|
|
}
|
||
|
|
}
|