Add feature tests for CSKH Excel import and database backup CLI commands
This commit is contained in:
1614
package-lock.json
generated
Normal file
1614
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
tests/Feature/ExportBackupTest.php
Normal file
55
tests/Feature/ExportBackupTest.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Product;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
test('export backup command exports database tables to json and writes manifest', function () {
|
||||||
|
// Create some data
|
||||||
|
User::factory()->count(3)->create();
|
||||||
|
Product::create(['name' => 'Sunrise Suite A1', 'status' => 'active']);
|
||||||
|
Product::create(['name' => 'Sunset Suite A2', 'status' => 'active']);
|
||||||
|
|
||||||
|
$backupPath = base_path('tests/Feature/backups_test');
|
||||||
|
|
||||||
|
// Clean backup directory if it exists
|
||||||
|
if (File::isDirectory($backupPath)) {
|
||||||
|
File::deleteDirectory($backupPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run backup command
|
||||||
|
$this->artisan('app:export-backup', [
|
||||||
|
'--path' => $backupPath,
|
||||||
|
'--tables' => 'users,products',
|
||||||
|
'--pretty' => true
|
||||||
|
])
|
||||||
|
->assertExitCode(0);
|
||||||
|
|
||||||
|
// Verify backup folder created
|
||||||
|
$directories = File::directories($backupPath);
|
||||||
|
expect($directories)->toHaveCount(1);
|
||||||
|
|
||||||
|
$backupDir = $directories[0];
|
||||||
|
|
||||||
|
// Verify files created
|
||||||
|
expect(File::exists("{$backupDir}/manifest.json"))->toBeTrue();
|
||||||
|
expect(File::exists("{$backupDir}/users.json"))->toBeTrue();
|
||||||
|
expect(File::exists("{$backupDir}/products.json"))->toBeTrue();
|
||||||
|
|
||||||
|
// Verify manifest contents
|
||||||
|
$manifest = json_decode(File::get("{$backupDir}/manifest.json"), true);
|
||||||
|
expect($manifest['tables']['users'])->toBe(3);
|
||||||
|
expect($manifest['tables']['products'])->toBe(2);
|
||||||
|
expect($manifest['total_rows'])->toBe(5);
|
||||||
|
|
||||||
|
// Verify products contents
|
||||||
|
$productsJson = json_decode(File::get("{$backupDir}/products.json"), true);
|
||||||
|
expect($productsJson)->toHaveCount(2);
|
||||||
|
expect($productsJson[0]['name'])->toBe('Sunrise Suite A1');
|
||||||
|
expect($productsJson[1]['name'])->toBe('Sunset Suite A2');
|
||||||
|
|
||||||
|
// Clean up backup directory
|
||||||
|
File::deleteDirectory($backupPath);
|
||||||
|
});
|
||||||
127
tests/Feature/ImportCskhTest.php
Normal file
127
tests/Feature/ImportCskhTest.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Contract;
|
||||||
|
use App\Models\Customer;
|
||||||
|
use App\Models\Feedback;
|
||||||
|
use App\Models\FeedbackChannel;
|
||||||
|
use App\Models\FeedbackInteraction;
|
||||||
|
use App\Models\Handover;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Models\ProductService;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Seed a default user since the importer associates interactions with the first user
|
||||||
|
User::factory()->create([
|
||||||
|
'name' => 'Default Agent',
|
||||||
|
'email' => 'agent@test.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create a default FeedbackChannel (ID 1) which is required by the importer
|
||||||
|
FeedbackChannel::create([
|
||||||
|
'name' => 'Email',
|
||||||
|
'slug' => 'email',
|
||||||
|
'icon' => 'heroicon-o-envelope',
|
||||||
|
'color' => '#3b82f6'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cskh import command processes rows correctly in dry run and active modes', function () {
|
||||||
|
// Create temporary CSV file inside the project directory so WSL has easy access
|
||||||
|
$csvPath = base_path('tests/Feature/cskh_test.csv');
|
||||||
|
|
||||||
|
// Headers: Mã căn hộ, Họ tên KH, BÀN GIÀO CĂN HỘ, TÌNH TRẠNG, HỢP ĐỒNG THUÊ, HĐ HỢP TÁC KINH DOANH, TỰ DOANH, Phí QL, Lịch Sử khách hàng
|
||||||
|
$headers = [
|
||||||
|
'Mã căn hộ',
|
||||||
|
'Họ tên KH',
|
||||||
|
'BÀN GIÀO CĂN HỘ',
|
||||||
|
'TÌNH TRẠNG',
|
||||||
|
'HỢP ĐỒNG THUÊ',
|
||||||
|
'HĐ HỢP TÁC KINH DOANH',
|
||||||
|
'TỰ DOANH',
|
||||||
|
'Phí QL',
|
||||||
|
'Lịch Sử khách hàng'
|
||||||
|
];
|
||||||
|
|
||||||
|
$row1 = [
|
||||||
|
'A1-101',
|
||||||
|
'Nguyen Van A',
|
||||||
|
'{"date":"2026-01-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"}',
|
||||||
|
'Đã bàn giao căn hộ cho KH',
|
||||||
|
'Số HĐ: HĐ-001, Giá: 10M, Thời hạn: 1 năm',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'Đã thu phí QL tháng 1',
|
||||||
|
"KH báo hỏng bóng đèn\nKỹ thuật đã sửa xong"
|
||||||
|
];
|
||||||
|
|
||||||
|
$row2 = [
|
||||||
|
'A1-102',
|
||||||
|
'Tran Thi B',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'HĐ hợp tác BCC-123',
|
||||||
|
'Doanh nghiệp B',
|
||||||
|
'',
|
||||||
|
'Khách hàng thắc mắc hóa đơn tiền nước'
|
||||||
|
];
|
||||||
|
|
||||||
|
$fp = fopen($csvPath, 'w');
|
||||||
|
// Write UTF-8 BOM for Excel support
|
||||||
|
fprintf($fp, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||||
|
fputcsv($fp, $headers);
|
||||||
|
fputcsv($fp, $row1);
|
||||||
|
fputcsv($fp, $row2);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
// 1. Dry run execution
|
||||||
|
$this->artisan('app:import-cskh', [
|
||||||
|
'file_path' => $csvPath,
|
||||||
|
'--dry-run' => true
|
||||||
|
])
|
||||||
|
->expectsOutputToContain('Total rows in file: 2')
|
||||||
|
->expectsOutputToContain('New Products')
|
||||||
|
->assertExitCode(0);
|
||||||
|
|
||||||
|
// Verify no records created in dry-run
|
||||||
|
expect(Product::count())->toBe(0);
|
||||||
|
expect(Customer::count())->toBe(0);
|
||||||
|
expect(Contract::count())->toBe(0);
|
||||||
|
expect(Handover::count())->toBe(0);
|
||||||
|
expect(ProductService::count())->toBe(0);
|
||||||
|
expect(Feedback::count())->toBe(0);
|
||||||
|
|
||||||
|
// 2. Active run execution
|
||||||
|
$this->artisan('app:import-cskh', [
|
||||||
|
'file_path' => $csvPath
|
||||||
|
])
|
||||||
|
->expectsOutputToContain('Total rows in file: 2')
|
||||||
|
->assertExitCode(0);
|
||||||
|
|
||||||
|
// Verify records created
|
||||||
|
expect(Product::count())->toBe(2);
|
||||||
|
expect(Customer::count())->toBe(2);
|
||||||
|
expect(Contract::count())->toBe(2); // 1 Lease (row1) and 1 BCC (row2)
|
||||||
|
expect(Handover::count())->toBe(1); // row1 has handover, row2 doesn't
|
||||||
|
expect(ProductService::count())->toBe(1); // row2 has Self-Business
|
||||||
|
expect(Feedback::count())->toBe(2); // both have feedback logs
|
||||||
|
expect(FeedbackInteraction::count())->toBe(3); // row1 has 2 lines, row2 has 1 line
|
||||||
|
|
||||||
|
// Verify relationships and fields
|
||||||
|
$productA = Product::where('name', 'A1-101')->first();
|
||||||
|
expect($productA)->not->toBeNull();
|
||||||
|
|
||||||
|
$handover = Handover::where('product_id', $productA->id)->first();
|
||||||
|
expect($handover)->not->toBeNull();
|
||||||
|
expect($handover->handover_date)->toBe('2026-01-01');
|
||||||
|
|
||||||
|
$contract = Contract::where('product_id', $productA->id)->first();
|
||||||
|
expect($contract)->not->toBeNull();
|
||||||
|
expect($contract->type)->toBe('lease'); // type is a string 'lease'
|
||||||
|
|
||||||
|
// Clean up temporary file
|
||||||
|
@unlink($csvPath);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user