apiKey = 'test-api-key-'.uniqid(); \DB::table('api_keys')->insert([ 'key' => $this->apiKey, 'description' => 'Test API Key', 'is_active' => true, 'created_at' => now(), 'updated_at' => now(), ]); // Tenant 생성 또는 기존 사용 $this->tenant = Tenant::first() ?? Tenant::withoutEvents(function () { return Tenant::create([ 'company_name' => 'Test Company', 'code' => 'TEST'.uniqid(), 'email' => 'test@example.com', 'phone' => '010-1234-5678', ]); }); // User 생성 $testUserId = 'testuser'.uniqid(); $this->user = User::create([ 'user_id' => $testUserId, 'name' => 'Test User', 'email' => $testUserId.'@example.com', 'password' => bcrypt('password123'), ]); // UserTenant 관계 생성 UserTenant::create([ 'user_id' => $this->user->id, 'tenant_id' => $this->tenant->id, 'is_active' => true, 'is_default' => true, ]); // Client 생성 $this->client = Client::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Test Client', 'client_code' => 'CLI'.uniqid(), 'created_by' => $this->user->id, ]); // 로그인 및 토큰 획득 $this->loginAndGetToken(); } protected function loginAndGetToken(): void { $response = $this->withHeaders([ 'X-API-KEY' => $this->apiKey, 'Accept' => 'application/json', ])->postJson('/api/v1/login', [ 'user_id' => $this->user->user_id, 'user_pwd' => 'password123', ]); $response->assertStatus(200); $this->token = $response->json('access_token'); } protected function authenticatedRequest(string $method, string $uri, array $data = []) { return $this->withHeaders([ 'X-API-KEY' => $this->apiKey, 'Authorization' => 'Bearer '.$this->token, 'Accept' => 'application/json', ])->{$method.'Json'}($uri, $data); } // ==================== BadDebt CRUD Tests ==================== public function test_can_list_bad_debts(): void { BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1000000, 'status' => BadDebt::STATUS_COLLECTING, 'overdue_days' => 30, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('get', '/api/v1/bad-debts'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'data', 'current_page', 'total', ], ]); } public function test_can_get_bad_debt_summary(): void { // 상태별 악성채권 생성 BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1000000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_RECOVERED, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('get', '/api/v1/bad-debts/summary'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'total_amount', 'collecting_amount', 'legal_action_amount', 'recovered_amount', 'bad_debt_amount', ], ]); $data = $response->json('data'); $this->assertEquals(1500000, $data['total_amount']); $this->assertEquals(1000000, $data['collecting_amount']); $this->assertEquals(500000, $data['recovered_amount']); } public function test_can_create_bad_debt(): void { $response = $this->authenticatedRequest('post', '/api/v1/bad-debts', [ 'client_id' => $this->client->id, 'debt_amount' => 2000000, 'status' => BadDebt::STATUS_COLLECTING, 'overdue_days' => 45, 'occurred_at' => now()->subDays(45)->format('Y-m-d'), ]); $response->assertStatus(201) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'id', 'client_id', 'debt_amount', 'status', ], ]); $this->assertDatabaseHas('bad_debts', [ 'client_id' => $this->client->id, 'debt_amount' => 2000000, 'status' => BadDebt::STATUS_COLLECTING, 'tenant_id' => $this->tenant->id, ]); } public function test_can_show_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1500000, 'status' => BadDebt::STATUS_LEGAL_ACTION, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('get', "/api/v1/bad-debts/{$badDebt->id}"); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'id', 'client_id', 'debt_amount', 'status', 'client', ], ]) ->assertJson([ 'data' => [ 'debt_amount' => '1500000.00', 'status' => BadDebt::STATUS_LEGAL_ACTION, ], ]); } public function test_can_update_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1000000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('put', "/api/v1/bad-debts/{$badDebt->id}", [ 'status' => BadDebt::STATUS_LEGAL_ACTION, 'debt_amount' => 1200000, ]); $response->assertStatus(200); $this->assertDatabaseHas('bad_debts', [ 'id' => $badDebt->id, 'status' => BadDebt::STATUS_LEGAL_ACTION, 'debt_amount' => 1200000, ]); } public function test_can_delete_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('delete', "/api/v1/bad-debts/{$badDebt->id}"); $response->assertStatus(200); $this->assertSoftDeleted('bad_debts', [ 'id' => $badDebt->id, ]); } public function test_can_toggle_bad_debt_active(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('patch', "/api/v1/bad-debts/{$badDebt->id}/toggle"); $response->assertStatus(200); $this->assertDatabaseHas('bad_debts', [ 'id' => $badDebt->id, 'is_active' => false, ]); // 다시 토글 $response = $this->authenticatedRequest('patch', "/api/v1/bad-debts/{$badDebt->id}/toggle"); $response->assertStatus(200); $this->assertDatabaseHas('bad_debts', [ 'id' => $badDebt->id, 'is_active' => true, ]); } // ==================== Document Tests ==================== public function test_can_add_document_to_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); // 테스트용 파일 생성 (files 테이블에) $fileId = \DB::table('files')->insertGetId([ 'tenant_id' => $this->tenant->id, 'original_name' => 'test.pdf', 'display_name' => 'test.pdf', 'stored_name' => 'test_'.uniqid().'.pdf', 'file_name' => 'test.pdf', 'mime_type' => 'application/pdf', 'file_size' => 1024, 'file_path' => 'uploads/test.pdf', 'created_by' => $this->user->id, 'created_at' => now(), 'updated_at' => now(), ]); $response = $this->authenticatedRequest('post', "/api/v1/bad-debts/{$badDebt->id}/documents", [ 'document_type' => 'tax_invoice', 'file_id' => $fileId, ]); $response->assertStatus(201); $this->assertDatabaseHas('bad_debt_documents', [ 'bad_debt_id' => $badDebt->id, 'document_type' => 'tax_invoice', 'file_id' => $fileId, ]); } public function test_can_remove_document_from_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $document = BadDebtDocument::create([ 'bad_debt_id' => $badDebt->id, 'document_type' => 'business_license', 'file_id' => 1, ]); $response = $this->authenticatedRequest('delete', "/api/v1/bad-debts/{$badDebt->id}/documents/{$document->id}"); $response->assertStatus(200); $this->assertDatabaseMissing('bad_debt_documents', [ 'id' => $document->id, ]); } // ==================== Memo Tests ==================== public function test_can_add_memo_to_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('post', "/api/v1/bad-debts/{$badDebt->id}/memos", [ 'content' => '첫 번째 추심 시도 - 연락 안됨', ]); $response->assertStatus(201); $this->assertDatabaseHas('bad_debt_memos', [ 'bad_debt_id' => $badDebt->id, 'content' => '첫 번째 추심 시도 - 연락 안됨', 'created_by' => $this->user->id, ]); } public function test_can_remove_memo_from_bad_debt(): void { $badDebt = BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $memo = BadDebtMemo::create([ 'bad_debt_id' => $badDebt->id, 'content' => '삭제할 메모', 'created_by' => $this->user->id, ]); $response = $this->authenticatedRequest('delete', "/api/v1/bad-debts/{$badDebt->id}/memos/{$memo->id}"); $response->assertStatus(200); $this->assertDatabaseMissing('bad_debt_memos', [ 'id' => $memo->id, ]); } // ==================== Filter Tests ==================== public function test_can_filter_bad_debts_by_status(): void { BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1000000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_RECOVERED, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('get', '/api/v1/bad-debts?status='.BadDebt::STATUS_COLLECTING); $response->assertStatus(200); $data = $response->json('data.data'); $this->assertCount(1, $data); $this->assertEquals(BadDebt::STATUS_COLLECTING, $data[0]['status']); } public function test_can_filter_bad_debts_by_client(): void { $client2 = Client::create([ 'tenant_id' => $this->tenant->id, 'name' => 'Another Client', 'client_code' => 'CLI2'.uniqid(), 'created_by' => $this->user->id, ]); BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $this->client->id, 'debt_amount' => 1000000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); BadDebt::create([ 'tenant_id' => $this->tenant->id, 'client_id' => $client2->id, 'debt_amount' => 500000, 'status' => BadDebt::STATUS_COLLECTING, 'is_active' => true, 'created_by' => $this->user->id, 'updated_by' => $this->user->id, ]); $response = $this->authenticatedRequest('get', '/api/v1/bad-debts?client_id='.$this->client->id); $response->assertStatus(200); $data = $response->json('data.data'); $this->assertCount(1, $data); $this->assertEquals($this->client->id, $data[0]['client_id']); } // ==================== Authentication Tests ==================== public function test_cannot_access_bad_debts_without_authentication(): void { $response = $this->withHeaders([ 'X-API-KEY' => $this->apiKey, 'Accept' => 'application/json', ])->getJson('/api/v1/bad-debts'); $response->assertStatus(401); } }