mirror of
https://github.com/snipe/snipe-it.git
synced 2025-01-12 06:17:28 -08:00
Merge pull request #15579 from bryanlopezinc/ImportTests
Add Import data tests
This commit is contained in:
commit
32551d55d7
|
@ -368,7 +368,7 @@ class Asset extends Depreciable
|
||||||
if ($this->save()) {
|
if ($this->save()) {
|
||||||
if (is_int($admin)) {
|
if (is_int($admin)) {
|
||||||
$checkedOutBy = User::findOrFail($admin);
|
$checkedOutBy = User::findOrFail($admin);
|
||||||
} elseif (get_class($admin) === \App\Models\User::class) {
|
} elseif ($admin && get_class($admin) === \App\Models\User::class) {
|
||||||
$checkedOutBy = $admin;
|
$checkedOutBy = $admin;
|
||||||
} else {
|
} else {
|
||||||
$checkedOutBy = auth()->user();
|
$checkedOutBy = auth()->user();
|
||||||
|
|
146
database/factories/ImportFactory.php
Normal file
146
database/factories/ImportFactory.php
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Import;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Tests\Support\Importing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends Factory<Import>
|
||||||
|
*/
|
||||||
|
class ImportFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected $model = Import::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->faker->company,
|
||||||
|
'file_path' => Str::random().'.csv',
|
||||||
|
'filesize' => $this->faker->randomDigitNotNull(),
|
||||||
|
'field_map' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an accessory import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function accessory()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\AccessoriesImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Accessories";
|
||||||
|
$attributes['import_type'] = 'Accessories';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an asset import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function asset()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\AssetsImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Assets";
|
||||||
|
$attributes['import_type'] = 'asset';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a component import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function component()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\ComponentsImportFileBuilder::times();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Components";
|
||||||
|
$attributes['import_type'] = 'component';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a consumable import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function consumable()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\ConsumablesImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Consumables";
|
||||||
|
$attributes['import_type'] = 'consumable';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a license import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function license()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\LicensesImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Licenses";
|
||||||
|
$attributes['import_type'] = 'license';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a users import type.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function users()
|
||||||
|
{
|
||||||
|
return $this->state(function (array $attributes) {
|
||||||
|
$fileBuilder = Importing\UsersImportFileBuilder::new();
|
||||||
|
|
||||||
|
$attributes['name'] = "{$attributes['name']} Employees";
|
||||||
|
$attributes['import_type'] = 'user';
|
||||||
|
$attributes['header_row'] = $fileBuilder->toCsv()[0];
|
||||||
|
$attributes['first_row'] = $fileBuilder->firstRow();
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,9 @@ use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use \Auth;
|
use \Auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends Factory<User>
|
||||||
|
*/
|
||||||
class UserFactory extends Factory
|
class UserFactory extends Factory
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
426
tests/Feature/Importing/Api/ImportAccessoriesTest.php
Normal file
426
tests/Feature/Importing/Api/ImportAccessoriesTest.php
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Accessory;
|
||||||
|
use App\Models\Actionlog;
|
||||||
|
use App\Models\Company;
|
||||||
|
use Database\Factories\AccessoryFactory;
|
||||||
|
use Database\Factories\CompanyFactory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\AccessoriesImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportAccessoriesTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'accessory';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportAccessories(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAccessoryPermissionCanImportAccessories(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->accessory()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importAccessory(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => [
|
||||||
|
'redirect_url' => route('accessories.index')
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newAccessory = Accessory::query()
|
||||||
|
->with(['location', 'category', 'manufacturer', 'supplier', 'company'])
|
||||||
|
->where('name', $row['itemName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$activityLog = Actionlog::query()
|
||||||
|
->where('item_type', Accessory::class)
|
||||||
|
->where('item_id', $newAccessory->id)
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($activityLog->action_type, 'create');
|
||||||
|
$this->assertEquals($activityLog->action_source, 'importer');
|
||||||
|
$this->assertEquals($activityLog->company_id, $newAccessory->company->id);
|
||||||
|
|
||||||
|
$this->assertEquals($newAccessory->name, $row['itemName']);
|
||||||
|
$this->assertEquals($newAccessory->qty, $row['quantity']);
|
||||||
|
$this->assertEquals($newAccessory->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newAccessory->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($newAccessory->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newAccessory->notes, $row['notes']);
|
||||||
|
$this->assertEquals($newAccessory->category->name, $row['category']);
|
||||||
|
$this->assertEquals($newAccessory->category->category_type, 'accessory');
|
||||||
|
$this->assertEquals($newAccessory->manufacturer->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($newAccessory->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($newAccessory->location->name, $row['location']);
|
||||||
|
$this->assertEquals($newAccessory->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($newAccessory->model_number, $row['modelNumber']);
|
||||||
|
$this->assertFalse($newAccessory->requestable);
|
||||||
|
$this->assertNull($newAccessory->min_amt);
|
||||||
|
$this->assertNull($newAccessory->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenImportFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['unknownColumn'] = $this->faker->word;
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willFormatDate(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['purchaseDate' => '2022/10/10']);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$accessory = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->firstRow()['itemName'])
|
||||||
|
->sole(['purchase_date']);
|
||||||
|
|
||||||
|
$this->assertEquals($accessory->purchase_date->toDateString(), '2022-10-10');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCategoryWhenCategoryExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessories = Accessory::query()
|
||||||
|
->whereIn('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAccessories->pluck('category_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewAccessoryWhenAccessoryWithNameExists(): void
|
||||||
|
{
|
||||||
|
$accessory = AccessoryFactory::new()->create(['name' => Str::random()]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(2)->replace(['itemName' => $accessory->name]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$probablyNewAccessories = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['name']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $probablyNewAccessories);
|
||||||
|
$this->assertEquals($probablyNewAccessories->first()->name, $accessory->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCompanyWhenCompanyAlreadyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessories = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['company_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAccessories->pluck('company_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewLocationWhenLocationAlreadyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessories = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['location_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAccessories->pluck('location_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewManufacturerWhenManufacturerAlreadyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessories = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['manufacturer_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAccessories->pluck('manufacturer_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewSupplierWhenSupplierAlreadyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['supplierName' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessories = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['supplier_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAccessories->pluck('supplier_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new()->forget(['minimumAmount', 'purchaseCost', 'purchaseDate']);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAccessory = Accessory::query()
|
||||||
|
->where('name', $importFileBuilder->firstRow()['itemName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertNull($newAccessory->min_amt);
|
||||||
|
$this->assertNull($newAccessory->purchase_date);
|
||||||
|
$this->assertNull($newAccessory->purchase_cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new()->forget(['itemName', 'quantity', 'category']);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
'' => [
|
||||||
|
'Accessory' => [
|
||||||
|
'name' => ['The name field is required.'],
|
||||||
|
'qty' => ['The qty field must be at least 1.'],
|
||||||
|
'category_id' => ['The category id field is required.']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateAccessoryFromImport(): void
|
||||||
|
{
|
||||||
|
$accessory = AccessoryFactory::new()->create(['name' => Str::random()])->refresh();
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['itemName' => $accessory->name]);
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedAccessory = Accessory::query()->find($accessory->id);
|
||||||
|
$updatedAttributes = [
|
||||||
|
'name', 'company_id', 'qty', 'purchase_date', 'purchase_cost',
|
||||||
|
'order_number', 'notes', 'category_id', 'manufacturer_id', 'supplier_id',
|
||||||
|
'location_id', 'model_number', 'updated_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($updatedAccessory->name, $row['itemName']);
|
||||||
|
$this->assertEquals($row['companyName'], $updatedAccessory->company->name);
|
||||||
|
$this->assertEquals($updatedAccessory->qty, $row['quantity']);
|
||||||
|
$this->assertEquals($updatedAccessory->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($updatedAccessory->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($updatedAccessory->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($updatedAccessory->notes, $row['notes']);
|
||||||
|
$this->assertEquals($updatedAccessory->category->name, $row['category']);
|
||||||
|
$this->assertEquals($updatedAccessory->category->category_type, 'accessory');
|
||||||
|
$this->assertEquals($updatedAccessory->manufacturer->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($updatedAccessory->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($updatedAccessory->location->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedAccessory->model_number, $row['modelNumber']);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
Arr::except($updatedAccessory->attributesToArray(), $updatedAttributes),
|
||||||
|
Arr::except($accessory->attributesToArray(), $updatedAttributes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenImportFileContainsEmptyValues(): void
|
||||||
|
{
|
||||||
|
$accessory = AccessoryFactory::new()->create(['name' => Str::random()]);
|
||||||
|
$accessory->refresh();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::new([
|
||||||
|
'companyName' => ' ',
|
||||||
|
'purchaseDate' => ' ',
|
||||||
|
'purchaseCost' => '',
|
||||||
|
'location' => '',
|
||||||
|
'companyName' => '',
|
||||||
|
'orderNumber' => '',
|
||||||
|
'category' => '',
|
||||||
|
'quantity' => '',
|
||||||
|
'manufacturerName' => '',
|
||||||
|
'supplierName' => '',
|
||||||
|
'notes' => '',
|
||||||
|
'requestAble' => '',
|
||||||
|
'minimumAmount' => '',
|
||||||
|
'modelNumber' => ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
$importFileBuilder->firstRow()['itemName'] => [
|
||||||
|
'Accessory' => [
|
||||||
|
'qty' => ['The qty field must be at least 1.'],
|
||||||
|
'category_id' => ['The category id field is required.']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$importFileBuilder->replace(['itemName' => $accessory->name]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedAccessory = clone $accessory;
|
||||||
|
$updatedAccessory->refresh();
|
||||||
|
|
||||||
|
$this->assertEquals($updatedAccessory->toArray(), $accessory->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::new()->definition();
|
||||||
|
$row = [
|
||||||
|
'itemName' => $faker['modelNumber'],
|
||||||
|
'purchaseDate' => $faker['notes'],
|
||||||
|
'purchaseCost' => $faker['location'],
|
||||||
|
'location' => $faker['purchaseCost'],
|
||||||
|
'companyName' => $faker['orderNumber'],
|
||||||
|
'orderNumber' => $faker['companyName'],
|
||||||
|
'category' => $faker['manufacturerName'],
|
||||||
|
'manufacturerName' => $faker['category'],
|
||||||
|
'notes' => $faker['purchaseDate'],
|
||||||
|
'minimumAmount' => $faker['supplierName'],
|
||||||
|
'modelNumber' => $faker['itemName'],
|
||||||
|
'quantity' => $faker['quantity']
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$import = ImportFactory::new()->accessory()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Item Name' => 'model_number',
|
||||||
|
'Purchase Date' => 'notes',
|
||||||
|
'Purchase Cost' => 'location',
|
||||||
|
'Location' => 'purchase_cost',
|
||||||
|
'Company' => 'order_number',
|
||||||
|
'Order Number' => 'company',
|
||||||
|
'Category' => 'manufacturer',
|
||||||
|
'Manufacturer' => 'category',
|
||||||
|
'Supplier' => 'min_amt',
|
||||||
|
'Notes' => 'purchase_date',
|
||||||
|
'Min QTY' => 'supplier',
|
||||||
|
'Model Number' => 'item_name',
|
||||||
|
'Quantity' => 'quantity'
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$newAccessory = Accessory::query()
|
||||||
|
->with(['location', 'category', 'manufacturer', 'supplier'])
|
||||||
|
->where('name', $row['modelNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newAccessory->name, $row['modelNumber']);
|
||||||
|
$this->assertEquals($newAccessory->model_number, $row['itemName']);
|
||||||
|
$this->assertEquals($newAccessory->qty, $row['quantity']);
|
||||||
|
$this->assertEquals($newAccessory->purchase_date->toDateString(), $row['notes']);
|
||||||
|
$this->assertEquals($newAccessory->purchase_cost, $row['location']);
|
||||||
|
$this->assertEquals($newAccessory->order_number, $row['companyName']);
|
||||||
|
$this->assertEquals($newAccessory->notes, $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newAccessory->category->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($newAccessory->manufacturer->name, $row['category']);
|
||||||
|
$this->assertEquals($newAccessory->location->name, $row['purchaseCost']);
|
||||||
|
}
|
||||||
|
}
|
601
tests/Feature/Importing/Api/ImportAssetsTest.php
Normal file
601
tests/Feature/Importing/Api/ImportAssetsTest.php
Normal file
|
@ -0,0 +1,601 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Actionlog as ActionLog;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\CustomField;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\CheckoutAssetNotification;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Database\Factories\AssetFactory;
|
||||||
|
use Database\Factories\CustomFieldFactory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\AssetsImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportAssetsTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'asset';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportAssets(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAssetsPermissionCanImportAssets(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importAsset(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => ['redirect_url' => route('hardware.index')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newAsset = Asset::query()
|
||||||
|
->with(['location', 'supplier', 'company', 'assignedAssets', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer'])
|
||||||
|
->where('serial', $row['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$assignee = User::query()->find($newAsset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']);
|
||||||
|
|
||||||
|
$activityLogs = ActionLog::query()
|
||||||
|
->where('item_type', Asset::class)
|
||||||
|
->where('item_id', $newAsset->id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(2, $activityLogs);
|
||||||
|
|
||||||
|
$this->assertEquals($activityLogs[0]->action_type, 'checkout');
|
||||||
|
$this->assertEquals($activityLogs[0]->item_type, Asset::class);
|
||||||
|
$this->assertEquals($activityLogs[0]->target_id, $assignee->id);
|
||||||
|
$this->assertEquals($activityLogs[0]->target_type, User::class);
|
||||||
|
$this->assertEquals($activityLogs[0]->note, 'Checkout from CSV Importer');
|
||||||
|
|
||||||
|
$this->assertEquals($activityLogs[1]->action_type, 'create');
|
||||||
|
$this->assertNull($activityLogs[1]->target_id);
|
||||||
|
$this->assertEquals($activityLogs[1]->item_type, Asset::class);
|
||||||
|
$this->assertNull($activityLogs[1]->note);
|
||||||
|
$this->assertNull($activityLogs[1]->target_type);
|
||||||
|
|
||||||
|
$this->assertEquals("{$assignee->first_name} {$assignee->last_name}", $row['assigneeFullName']);
|
||||||
|
$this->assertEquals($assignee->email, $row['assigneeEmail']);
|
||||||
|
$this->assertEquals($assignee->username, $row['assigneeUsername']);
|
||||||
|
|
||||||
|
$this->assertEquals($newAsset->model->category->name, $row['category']);
|
||||||
|
$this->assertEquals($newAsset->model->manufacturer->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($newAsset->name, $row['itemName']);
|
||||||
|
$this->assertEquals($newAsset->asset_tag, $row['tag']);
|
||||||
|
$this->assertEquals($newAsset->model->name, $row['model']);
|
||||||
|
$this->assertEquals($newAsset->model->model_number, $row['modelNumber']);
|
||||||
|
$this->assertEquals($newAsset->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertNull($newAsset->asset_eol_date);
|
||||||
|
$this->assertEquals(0, $newAsset->eol_explicit);
|
||||||
|
$this->assertEquals($newAsset->location_id, $newAsset->rtd_location_id);
|
||||||
|
$this->assertEquals($newAsset->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertNull($newAsset->order_number);
|
||||||
|
$this->assertEquals($newAsset->image, '');
|
||||||
|
$this->assertNull($newAsset->user_id);
|
||||||
|
$this->assertEquals($newAsset->physical, 1);
|
||||||
|
$this->assertEquals($newAsset->assetStatus->name, $row['status']);
|
||||||
|
$this->assertEquals($newAsset->archived, 0);
|
||||||
|
$this->assertEquals($newAsset->warranty_months, $row['warrantyInMonths']);
|
||||||
|
$this->assertNull($newAsset->deprecate);
|
||||||
|
$this->assertEquals($newAsset->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($newAsset->requestable, 0);
|
||||||
|
$this->assertEquals($newAsset->defaultLoc->name, $row['location']);
|
||||||
|
$this->assertEquals($newAsset->accepted, null);
|
||||||
|
$this->assertEquals(Carbon::parse($newAsset->last_checkout)->toDateString(), now()->toDateString());
|
||||||
|
$this->assertEquals($newAsset->last_checkin, 0);
|
||||||
|
$this->assertEquals($newAsset->expected_checkin, 0);
|
||||||
|
$this->assertEquals($newAsset->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($newAsset->assigned_type, User::class);
|
||||||
|
$this->assertNull($newAsset->last_audit_date);
|
||||||
|
$this->assertNull($newAsset->next_audit_date);
|
||||||
|
$this->assertEquals($newAsset->location->name, $row['location']);
|
||||||
|
$this->assertEquals($newAsset->checkin_counter, 0);
|
||||||
|
$this->assertEquals($newAsset->checkout_counter, 1);
|
||||||
|
$this->assertEquals($newAsset->requests_counter, 0);
|
||||||
|
$this->assertEquals($newAsset->byod, 0);
|
||||||
|
|
||||||
|
//Notes is never read.
|
||||||
|
//$this->assertEquals($asset->notes, $row['notes']);
|
||||||
|
|
||||||
|
Notification::assertSentTo($assignee, CheckoutAssetNotification::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['unknownColumnInCsvFile'] = 'foo';
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewAssetWhenAssetWithSameTagAlreadyExists(): void
|
||||||
|
{
|
||||||
|
$asset = AssetFactory::new()->create(['asset_tag' => $this->faker->uuid]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['tag' => $asset->asset_tag]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
'' => [
|
||||||
|
'asset_tag' => [
|
||||||
|
'asset_tag' => [
|
||||||
|
"An asset with the asset tag {$asset->asset_tag} already exists and an update was not requested. No change was made."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$assetsWithSameTag = Asset::query()->where('asset_tag', $asset->asset_tag)->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $assetsWithSameTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCompanyWhenCompanyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('company_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewLocationWhenLocationExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('location_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewSupplierWhenSupplierExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['supplierName' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['supplier_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('supplier_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewManufacturerWhenManufacturerExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->with('model.manufacturer')
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('model.manufacturer_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateCategoryWhenCategoryExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->with('model.category')
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('model.category_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewAssetModelWhenAssetModelExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['model' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->with('model')
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $newAssets->pluck('model.name')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times()->forget([
|
||||||
|
'purchaseCost',
|
||||||
|
'purchaseDate',
|
||||||
|
'status'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAsset = Asset::query()
|
||||||
|
->with(['assetStatus'])
|
||||||
|
->where('serial', $importFileBuilder->firstRow()['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newAsset->assetStatus->name, 'Ready to Deploy');
|
||||||
|
$this->assertNull($newAsset->purchase_date);
|
||||||
|
$this->assertNull($newAsset->purchase_cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willFormatValues(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new([
|
||||||
|
'warrantyInMonths' => '3 months',
|
||||||
|
'purchaseDate' => '2022/10/10'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAsset = Asset::query()
|
||||||
|
->where('serial', $importFileBuilder->firstRow()['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newAsset->warranty_months, 3);
|
||||||
|
$this->assertEquals($newAsset->purchase_date->toDateString(), '2022-10-10');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(2)
|
||||||
|
->forget(['tag'])
|
||||||
|
->replace(['model' => '']);
|
||||||
|
|
||||||
|
$rows = $importFileBuilder->all();
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
$rows[0]['itemName'] => [
|
||||||
|
"Asset \"{$rows[0]['itemName']}\"" => [
|
||||||
|
'asset_tag' => [
|
||||||
|
'The asset tag field must be at least 1 characters.',
|
||||||
|
],
|
||||||
|
'model_id' => [
|
||||||
|
'The model id field is required.'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
$rows[1]['itemName'] => [
|
||||||
|
"Asset \"{$rows[1]['itemName']}\"" => [
|
||||||
|
'asset_tag' => [
|
||||||
|
'The asset tag field must be at least 1 characters.',
|
||||||
|
],
|
||||||
|
'model_id' => [
|
||||||
|
'The model id field is required.'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newAssets = Asset::query()
|
||||||
|
->whereIn('serial', Arr::pluck($rows, 'serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(0, $newAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateAssetFromImport(): void
|
||||||
|
{
|
||||||
|
$asset = AssetFactory::new()->create()->refresh();
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(1)->replace(['tag' => $asset->asset_tag]);
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedAsset = Asset::query()
|
||||||
|
->with(['location', 'supplier', 'company', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer'])
|
||||||
|
->find($asset->id);
|
||||||
|
|
||||||
|
$assignee = User::query()->find($updatedAsset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']);
|
||||||
|
|
||||||
|
$updatedAttributes = [
|
||||||
|
'category', 'manufacturer_id', 'name', 'tag', 'model_id',
|
||||||
|
'model_number', 'purchase_date', 'purchase_cost', 'warranty_months', 'supplier_id',
|
||||||
|
'location_id', 'company_id', 'serial', 'assigned_to', 'status_id', 'rtd_location_id',
|
||||||
|
'last_checkout', 'requestable', 'updated_at', 'checkout_counter', 'assigned_type'
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals("{$assignee->first_name} {$assignee->last_name}", $row['assigneeFullName']);
|
||||||
|
$this->assertEquals($assignee->email, $row['assigneeEmail']);
|
||||||
|
$this->assertEquals($assignee->username, $row['assigneeUsername']);
|
||||||
|
|
||||||
|
$this->assertEquals($updatedAsset->model->category->name, $row['category']);
|
||||||
|
$this->assertEquals($updatedAsset->model->manufacturer->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($updatedAsset->name, $row['itemName']);
|
||||||
|
$this->assertEquals($updatedAsset->asset_tag, $row['tag']);
|
||||||
|
$this->assertEquals($updatedAsset->model->name, $row['model']);
|
||||||
|
$this->assertEquals($updatedAsset->model->model_number, $row['modelNumber']);
|
||||||
|
$this->assertEquals($updatedAsset->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($updatedAsset->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($updatedAsset->assetStatus->name, $row['status']);
|
||||||
|
$this->assertEquals($updatedAsset->warranty_months, $row['warrantyInMonths']);
|
||||||
|
$this->assertEquals($updatedAsset->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($updatedAsset->defaultLoc->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedAsset->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($updatedAsset->location->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedAsset->checkout_counter, 1);
|
||||||
|
$this->assertEquals($updatedAsset->assigned_type, user::class);
|
||||||
|
|
||||||
|
//RequestAble is always updated regardless of initial value.
|
||||||
|
//$this->assertEquals($updatedAsset->requestable, $asset->requestable);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
Arr::except($updatedAsset->attributesToArray(), $updatedAttributes),
|
||||||
|
Arr::except($asset->attributesToArray(), $updatedAttributes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::new()->definition();
|
||||||
|
$row = [
|
||||||
|
'assigneeFullName' => $faker['supplierName'],
|
||||||
|
'assigneeEmail' => $faker['manufacturerName'],
|
||||||
|
'assigneeUsername' => $faker['serialNumber'],
|
||||||
|
'category' => $faker['location'],
|
||||||
|
'companyName' => $faker['purchaseCost'],
|
||||||
|
'itemName' => $faker['modelNumber'],
|
||||||
|
'location' => $faker['assigneeUsername'],
|
||||||
|
'manufacturerName' => $faker['status'],
|
||||||
|
'model' => $faker['itemName'],
|
||||||
|
'modelNumber' => $faker['category'],
|
||||||
|
'notes' => $faker['notes'],
|
||||||
|
'purchaseCost' => $faker['model'],
|
||||||
|
'purchaseDate' => $faker['companyName'],
|
||||||
|
'serialNumber' => $faker['tag'],
|
||||||
|
'supplierName' => $faker['purchaseDate'],
|
||||||
|
'status' => $faker['warrantyInMonths'],
|
||||||
|
'tag' => $faker['assigneeEmail'],
|
||||||
|
'warrantyInMonths' => $faker['assigneeFullName'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Asset Tag' => 'email',
|
||||||
|
'Category' => 'location',
|
||||||
|
'Company' => 'purchase_cost',
|
||||||
|
'Email' => 'manufacturer',
|
||||||
|
'Full Name' => 'supplier',
|
||||||
|
'Item Name' => 'model_number',
|
||||||
|
'Location' => 'username',
|
||||||
|
'Manufacturer' => 'status',
|
||||||
|
'Model name' => 'item_name',
|
||||||
|
'Model Number' => 'category',
|
||||||
|
'Notes' => 'asset_notes',
|
||||||
|
'Purchase Cost' => 'asset_model',
|
||||||
|
'Purchase Date' => 'company',
|
||||||
|
'Serial number' => 'asset_tag',
|
||||||
|
'Status' => 'warranty_months',
|
||||||
|
'Supplier' => 'purchase_date',
|
||||||
|
'Username' => 'serial',
|
||||||
|
'Warranty' => 'full_name',
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$asset = Asset::query()
|
||||||
|
->with(['location', 'supplier', 'company', 'assignedAssets', 'defaultLoc', 'assetStatus', 'model.category', 'model.manufacturer'])
|
||||||
|
->where('serial', $row['assigneeUsername'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$assignee = User::query()->find($asset->assigned_to, ['id', 'first_name', 'last_name', 'email', 'username']);
|
||||||
|
|
||||||
|
$this->assertEquals("{$assignee->first_name} {$assignee->last_name}", $row['warrantyInMonths']);
|
||||||
|
$this->assertEquals($assignee->email, $row['tag']);
|
||||||
|
$this->assertEquals($assignee->username, $row['location']);
|
||||||
|
|
||||||
|
$this->assertEquals($asset->model->category->name, $row['modelNumber']);
|
||||||
|
$this->assertEquals($asset->model->manufacturer->name, $row['assigneeEmail']);
|
||||||
|
$this->assertEquals($asset->name, $row['model']);
|
||||||
|
$this->assertEquals($asset->asset_tag, $row['serialNumber']);
|
||||||
|
$this->assertEquals($asset->model->name, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($asset->model->model_number, $row['itemName']);
|
||||||
|
$this->assertEquals($asset->purchase_date->toDateString(), $row['supplierName']);
|
||||||
|
$this->assertEquals($asset->purchase_cost, $row['companyName']);
|
||||||
|
$this->assertEquals($asset->assetStatus->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($asset->warranty_months, $row['status']);
|
||||||
|
$this->assertEquals($asset->supplier->name, $row['assigneeFullName']);
|
||||||
|
$this->assertEquals($asset->defaultLoc->name, $row['category']);
|
||||||
|
$this->assertEquals($asset->company->name, $row['purchaseDate']);
|
||||||
|
$this->assertEquals($asset->location->name, $row['category']);
|
||||||
|
$this->assertEquals($asset->notes, $row['notes']);
|
||||||
|
$this->assertNull($asset->asset_eol_date);
|
||||||
|
$this->assertEquals(0, $asset->eol_explicit);
|
||||||
|
$this->assertNull($asset->order_number);
|
||||||
|
$this->assertEquals($asset->image, '');
|
||||||
|
$this->assertNull($asset->user_id);
|
||||||
|
$this->assertEquals($asset->physical, 1);
|
||||||
|
$this->assertEquals($asset->archived, 0);
|
||||||
|
$this->assertNull($asset->deprecate);
|
||||||
|
$this->assertEquals($asset->requestable, 0);
|
||||||
|
$this->assertEquals($asset->accepted, null);
|
||||||
|
$this->assertEquals(Carbon::parse($asset->last_checkout)->toDateString(), now()->toDateString());
|
||||||
|
$this->assertEquals($asset->last_checkin, 0);
|
||||||
|
$this->assertEquals($asset->expected_checkin, 0);
|
||||||
|
$this->assertEquals($asset->assigned_type, User::class);
|
||||||
|
$this->assertNull($asset->last_audit_date);
|
||||||
|
$this->assertNull($asset->next_audit_date);
|
||||||
|
$this->assertEquals($asset->checkin_counter, 0);
|
||||||
|
$this->assertEquals($asset->checkout_counter, 1);
|
||||||
|
$this->assertEquals($asset->requests_counter, 0);
|
||||||
|
$this->assertEquals($asset->byod, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customFields(): void
|
||||||
|
{
|
||||||
|
$macAddress = $this->faker->macAddress;
|
||||||
|
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['Mac Address'] = $macAddress;
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$customField = CustomField::query()->where('name', 'Mac Address')->firstOrNew();
|
||||||
|
|
||||||
|
if (!$customField->exists) {
|
||||||
|
$customField = CustomFieldFactory::new()->macAddress()->create(['db_column' => '_snipeit_mac_address_1']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($customField->field_encrypted) {
|
||||||
|
$customField->field_encrypted = 0;
|
||||||
|
$customField->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newAsset = Asset::query()->where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newAsset->getAttribute($customField->db_column), $macAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willEncryptCustomFields(): void
|
||||||
|
{
|
||||||
|
$macAddress = $this->faker->macAddress;
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
|
||||||
|
$row['Mac Address'] = $macAddress;
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$customField = CustomField::query()->where('name', 'Mac Address')->firstOrNew();
|
||||||
|
|
||||||
|
if (!$customField->exists) {
|
||||||
|
$customField = CustomFieldFactory::new()->macAddress()->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$customField->field_encrypted) {
|
||||||
|
$customField->field_encrypted = 1;
|
||||||
|
$customField->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->asset()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$asset = Asset::query()->where('serial', $importFileBuilder->firstRow()['serialNumber'])->sole();
|
||||||
|
$encryptedMacAddress = $asset->getAttribute($customField->db_column);
|
||||||
|
|
||||||
|
$this->assertNotEquals($encryptedMacAddress, $macAddress);
|
||||||
|
}
|
||||||
|
}
|
309
tests/Feature/Importing/Api/ImportComponentsTest.php
Normal file
309
tests/Feature/Importing/Api/ImportComponentsTest.php
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Actionlog as ActionLog;
|
||||||
|
use App\Models\Component;
|
||||||
|
use Database\Factories\ComponentFactory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\ComponentsImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportComponentsTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'component';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportComponents(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAssetsPermissionCanImportComponents(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->component()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importComponents(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => ['redirect_url' => route('components.index')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newComponent = Component::query()
|
||||||
|
->with(['location', 'category', 'company'])
|
||||||
|
->where('name', $row['itemName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$activityLog = ActionLog::query()
|
||||||
|
->where('item_type', Component::class)
|
||||||
|
->where('item_id', $newComponent->id)
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($activityLog->action_type, 'create');
|
||||||
|
$this->assertEquals($activityLog->action_source, 'importer');
|
||||||
|
$this->assertEquals($activityLog->company_id, $newComponent->company->id);
|
||||||
|
|
||||||
|
$this->assertEquals($newComponent->name, $row['itemName']);
|
||||||
|
$this->assertEquals($newComponent->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($newComponent->category->name, $row['category']);
|
||||||
|
$this->assertEquals($newComponent->location->name, $row['location']);
|
||||||
|
$this->assertNull($newComponent->supplier_id);
|
||||||
|
$this->assertEquals($newComponent->qty, $row['quantity']);
|
||||||
|
$this->assertEquals($newComponent->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newComponent->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newComponent->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertNull($newComponent->min_amt);
|
||||||
|
$this->assertEquals($newComponent->serial, $row['serialNumber']);
|
||||||
|
$this->assertNull($newComponent->image);
|
||||||
|
$this->assertNull($newComponent->notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->firstRow();
|
||||||
|
$row['unknownColumnInCsvFile'] = 'foo';
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewComponentWhenComponentWithNameAndSerialNumberExists(): void
|
||||||
|
{
|
||||||
|
$component = ComponentFactory::new()->create();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace([
|
||||||
|
'itemName' => $component->name,
|
||||||
|
'serialNumber' => $component->serial
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$probablyNewComponents = Component::query()
|
||||||
|
->where('name', $component->name)
|
||||||
|
->where('serial', $component->serial)
|
||||||
|
->get(['id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $probablyNewComponents);
|
||||||
|
$this->assertEquals($component->id, $probablyNewComponents->sole()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCompanyWhenCompanyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newComponents = Component::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['company_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newComponents->pluck('company_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewLocationWhenLocationExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newComponents = Component::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['location_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newComponents->pluck('location_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCategoryWhenCategoryExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newComponents = Component::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['category_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newComponents->pluck('category_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new()
|
||||||
|
->replace(['category' => ''])
|
||||||
|
->forget(['quantity']);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
$row['itemName'] => [
|
||||||
|
'Component' => [
|
||||||
|
'qty' => ['The qty field must be at least 1.'],
|
||||||
|
'category_id' => ['The category id field is required.']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newComponents = Component::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(0, $newComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateComponentFromImport(): void
|
||||||
|
{
|
||||||
|
$component = ComponentFactory::new()->create();
|
||||||
|
$importFileBuilder = ImportFileBuilder::new([
|
||||||
|
'itemName' => $component->name,
|
||||||
|
'serialNumber' => $component->serial
|
||||||
|
]);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedComponent = Component::query()
|
||||||
|
->with(['location', 'category'])
|
||||||
|
->where('serial', $row['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($updatedComponent->name, $row['itemName']);
|
||||||
|
$this->assertEquals($updatedComponent->category->name, $row['category']);
|
||||||
|
$this->assertEquals($updatedComponent->location->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedComponent->supplier_id, $component->supplier_id);
|
||||||
|
$this->assertEquals($updatedComponent->qty, $row['quantity']);
|
||||||
|
$this->assertEquals($updatedComponent->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($updatedComponent->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($updatedComponent->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($updatedComponent->min_amt, $component->min_amt);
|
||||||
|
$this->assertEquals($updatedComponent->serial, $row['serialNumber']);
|
||||||
|
$this->assertEquals($updatedComponent->image, $component->image);
|
||||||
|
$this->assertEquals($updatedComponent->notes, $component->notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::new()->definition();
|
||||||
|
$row = [
|
||||||
|
'category' => $faker['serialNumber'],
|
||||||
|
'companyName' => $faker['quantity'],
|
||||||
|
'itemName' => $faker['purchaseDate'],
|
||||||
|
'location' => $faker['purchaseCost'],
|
||||||
|
'orderNumber' => $faker['orderNumber'],
|
||||||
|
'purchaseCost' => $faker['category'],
|
||||||
|
'purchaseDate' => $faker['companyName'],
|
||||||
|
'quantity' => $faker['itemName'],
|
||||||
|
'serialNumber' => $faker['location']
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$import = ImportFactory::new()->component()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Category' => 'serial',
|
||||||
|
'Company' => 'quantity',
|
||||||
|
'item Name' => 'purchase_date',
|
||||||
|
'Location' => 'purchase_cost',
|
||||||
|
'Order Number' => 'order_number',
|
||||||
|
'Purchase Cost' => 'category',
|
||||||
|
'Purchase Date' => 'company',
|
||||||
|
'Quantity' => 'item_name',
|
||||||
|
'Serial number' => 'location',
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$newComponent = Component::query()
|
||||||
|
->with(['location', 'category'])
|
||||||
|
->where('serial', $importFileBuilder->firstRow()['category'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newComponent->name, $row['quantity']);
|
||||||
|
$this->assertEquals($newComponent->category->name, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($newComponent->location->name, $row['serialNumber']);
|
||||||
|
$this->assertNull($newComponent->supplier_id);
|
||||||
|
$this->assertEquals($newComponent->qty, $row['companyName']);
|
||||||
|
$this->assertEquals($newComponent->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newComponent->purchase_date->toDateString(), $row['itemName']);
|
||||||
|
$this->assertEquals($newComponent->purchase_cost, $row['location']);
|
||||||
|
$this->assertNull($newComponent->min_amt);
|
||||||
|
$this->assertNull($newComponent->image);
|
||||||
|
$this->assertNull($newComponent->notes);
|
||||||
|
}
|
||||||
|
}
|
309
tests/Feature/Importing/Api/ImportConsumablesTest.php
Normal file
309
tests/Feature/Importing/Api/ImportConsumablesTest.php
Normal file
|
@ -0,0 +1,309 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Actionlog as ActivityLog;
|
||||||
|
use App\Models\Consumable;
|
||||||
|
use Database\Factories\ConsumableFactory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\ConsumablesImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportConsumablesTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'consumable';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportConsumables(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAssetsPermissionCanImportConsumables(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->consumable()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importConsumables(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => ['redirect_url' => route('consumables.index')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newConsumable = Consumable::query()
|
||||||
|
->with(['location', 'category', 'company'])
|
||||||
|
->where('name', $row['itemName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$activityLog = ActivityLog::query()
|
||||||
|
->where('item_type', Consumable::class)
|
||||||
|
->where('item_id', $newConsumable->id)
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($activityLog->action_type, 'create');
|
||||||
|
$this->assertEquals($activityLog->action_source, 'importer');
|
||||||
|
$this->assertEquals($activityLog->company_id, $newConsumable->company->id);
|
||||||
|
|
||||||
|
$this->assertEquals($newConsumable->name, $row['itemName']);
|
||||||
|
$this->assertEquals($newConsumable->category->name, $row['category']);
|
||||||
|
$this->assertEquals($newConsumable->location->name, $row['location']);
|
||||||
|
$this->assertEquals($newConsumable->company->name, $row['companyName']);
|
||||||
|
$this->assertNull($newConsumable->supplier_id);
|
||||||
|
$this->assertFalse($newConsumable->requestable);
|
||||||
|
$this->assertNull($newConsumable->image);
|
||||||
|
$this->assertEquals($newConsumable->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newConsumable->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newConsumable->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertNull($newConsumable->min_amt);
|
||||||
|
$this->assertEquals($newConsumable->model_number, '');
|
||||||
|
$this->assertNull($newConsumable->item_number);
|
||||||
|
$this->assertNull($newConsumable->manufacturer_id);
|
||||||
|
$this->assertNull($newConsumable->notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['unknownColumnInCsvFile'] = 'foo';
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewConsumableWhenConsumableNameAlreadyExist(): void
|
||||||
|
{
|
||||||
|
$consumable = ConsumableFactory::new()->create(['name' => Str::random()]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['itemName' => $consumable->name]);
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$probablyNewConsumables = Consumable::query()
|
||||||
|
->where('name', $consumable->name)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $probablyNewConsumables);
|
||||||
|
$this->assertEquals($consumable->id, $probablyNewConsumables->sole()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCompanyWhenCompanyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newConsumables = Consumable::query()
|
||||||
|
->whereIn('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['company_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newConsumables->pluck('company_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewLocationWhenLocationExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['location' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newConsumables = Consumable::query()
|
||||||
|
->whereIn('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['location_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newConsumables->pluck('location_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCategoryWhenCategoryExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newConsumables = Consumable::query()
|
||||||
|
->whereIn('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['category_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newConsumables->pluck('category_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['category' => ''])->forget(['quantity', 'name']);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
$row['itemName'] => [
|
||||||
|
'Consumable' => [
|
||||||
|
'category_id' => ['The category id field is required.']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newConsumables = Consumable::query()
|
||||||
|
->whereIn('name', $importFileBuilder->pluck('itemName'))
|
||||||
|
->get(['id']);
|
||||||
|
|
||||||
|
$this->assertCount(0, $newConsumables);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateConsumableFromImport(): void
|
||||||
|
{
|
||||||
|
$consumable = ConsumableFactory::new()->create(['name' => Str::random()]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['itemName' => $consumable->name]);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedConsumable = Consumable::query()
|
||||||
|
->with(['location', 'category', 'company'])
|
||||||
|
->where('name', $importFileBuilder->firstRow()['itemName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($updatedConsumable->name, $row['itemName']);
|
||||||
|
$this->assertEquals($updatedConsumable->category->name, $row['category']);
|
||||||
|
$this->assertEquals($updatedConsumable->location->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedConsumable->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($updatedConsumable->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($updatedConsumable->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($updatedConsumable->purchase_cost, $row['purchaseCost']);
|
||||||
|
|
||||||
|
$this->assertEquals($consumable->supplier_id, $updatedConsumable->supplier_id);
|
||||||
|
$this->assertEquals($consumable->requestable, $updatedConsumable->requestable);
|
||||||
|
$this->assertEquals($consumable->min_amt, $updatedConsumable->min_amt);
|
||||||
|
$this->assertEquals($consumable->model_number, $updatedConsumable->model_number);
|
||||||
|
$this->assertEquals($consumable->item_number, $updatedConsumable->item_number);
|
||||||
|
$this->assertEquals($consumable->manufacturer_id, $updatedConsumable->manufacturer_id);
|
||||||
|
$this->assertEquals($consumable->notes, $updatedConsumable->notes);
|
||||||
|
$this->assertEquals($consumable->item_number, $updatedConsumable->item_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::new()->definition();
|
||||||
|
$row = [
|
||||||
|
'category' => $faker['supplier'],
|
||||||
|
'companyName' => $faker['quantity'],
|
||||||
|
'itemName' => $faker['purchaseDate'],
|
||||||
|
'location' => $faker['purchaseCost'],
|
||||||
|
'orderNumber' => $faker['orderNumber'],
|
||||||
|
'purchaseCost' => $faker['location'],
|
||||||
|
'purchaseDate' => $faker['companyName'],
|
||||||
|
'quantity' => $faker['itemName'],
|
||||||
|
'supplier' => $faker['category']
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->consumable()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Category' => 'supplier',
|
||||||
|
'Company' => 'quantity',
|
||||||
|
'item Name' => 'purchase_date',
|
||||||
|
'Location' => 'purchase_cost',
|
||||||
|
'Order Number' => 'order_number',
|
||||||
|
'Purchase Cost' => 'location',
|
||||||
|
'Purchase Date' => 'company',
|
||||||
|
'Quantity' => 'item_name',
|
||||||
|
'Supplier' => 'category',
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$newConsumable = Consumable::query()
|
||||||
|
->with(['location', 'category', 'company'])
|
||||||
|
->where('name', $importFileBuilder->firstRow()['quantity'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newConsumable->category->name, $row['supplier']);
|
||||||
|
$this->assertEquals($newConsumable->location->name, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($newConsumable->company->name, $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newConsumable->qty, $row['companyName']);
|
||||||
|
$this->assertEquals($newConsumable->name, $row['quantity']);
|
||||||
|
$this->assertNull($newConsumable->supplier_id);
|
||||||
|
$this->assertFalse($newConsumable->requestable);
|
||||||
|
$this->assertNull($newConsumable->image);
|
||||||
|
$this->assertEquals($newConsumable->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newConsumable->purchase_date->toDateString(), $row['itemName']);
|
||||||
|
$this->assertEquals($newConsumable->purchase_cost, $row['location']);
|
||||||
|
$this->assertNull($newConsumable->min_amt);
|
||||||
|
$this->assertEquals($newConsumable->model_number, '');
|
||||||
|
$this->assertNull($newConsumable->item_number);
|
||||||
|
$this->assertNull($newConsumable->manufacturer_id);
|
||||||
|
$this->assertNull($newConsumable->notes);
|
||||||
|
}
|
||||||
|
}
|
36
tests/Feature/Importing/Api/ImportDataTestCase.php
Normal file
36
tests/Feature/Importing/Api/ImportDataTestCase.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use Tests\TestCase;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
|
||||||
|
abstract class ImportDataTestCase extends TestCase
|
||||||
|
{
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
return $this->postJson(route('api.imports.importFile', $parameters), $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Add more permissions.
|
||||||
|
*/
|
||||||
|
public static function permissionsTestData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'`admin`' => ['admin'],
|
||||||
|
'`reports.view`' => ['reports.view'],
|
||||||
|
'only `assets` permission' => [
|
||||||
|
'assets.view',
|
||||||
|
'assets.create',
|
||||||
|
'assets.edit',
|
||||||
|
'assets.delete',
|
||||||
|
'assets.checkout',
|
||||||
|
'assets.checkin',
|
||||||
|
'assets.audit',
|
||||||
|
'assets.view.requestable',
|
||||||
|
'assets.view.encrypted_custom_fields'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
360
tests/Feature/Importing/Api/ImportLicenseTest.php
Normal file
360
tests/Feature/Importing/Api/ImportLicenseTest.php
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Actionlog as ActivityLog;
|
||||||
|
use App\Models\License;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use Database\Factories\LicenseFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\LicensesImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportLicenseTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'license';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportLicenses(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAssetsPermissionCanImportLicenses(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->license()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importLicenses(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => ['redirect_url' => route('licenses.index')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newLicense = License::query()
|
||||||
|
->withCasts(['reassignable' => 'bool'])
|
||||||
|
->with(['category', 'company', 'manufacturer', 'supplier'])
|
||||||
|
->where('serial', $row['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$activityLogs = ActivityLog::query()
|
||||||
|
->where('item_type', License::class)
|
||||||
|
->where('item_id', $newLicense->id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(2, $activityLogs);
|
||||||
|
|
||||||
|
$this->assertEquals($newLicense->name, $row['licenseName']);
|
||||||
|
$this->assertEquals($newLicense->serial, $row['serialNumber']);
|
||||||
|
$this->assertEquals($newLicense->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($newLicense->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($newLicense->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($newLicense->seats, $row['seats']);
|
||||||
|
$this->assertEquals($newLicense->notes, $row['notes']);
|
||||||
|
$this->assertEquals($newLicense->license_name, $row['licensedToName']);
|
||||||
|
$this->assertEquals($newLicense->license_email, $row['licensedToEmail']);
|
||||||
|
$this->assertEquals($newLicense->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($newLicense->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($newLicense->category->name, $row['category']);
|
||||||
|
$this->assertEquals($newLicense->expiration_date->toDateString(), $row['expirationDate']);
|
||||||
|
$this->assertEquals($newLicense->maintained, $row['isMaintained'] === 'TRUE');
|
||||||
|
$this->assertEquals($newLicense->reassignable, $row['isReassignAble'] === 'TRUE');
|
||||||
|
$this->assertEquals($newLicense->purchase_order, '');
|
||||||
|
$this->assertNull($newLicense->depreciation_id);
|
||||||
|
$this->assertNull($newLicense->termination_date);
|
||||||
|
$this->assertNull($newLicense->deprecate);
|
||||||
|
$this->assertNull($newLicense->min_amt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['unknownColumnInCsvFile'] = 'foo';
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewLicenseWhenNameAndSerialNumberAlreadyExist(): void
|
||||||
|
{
|
||||||
|
$license = LicenseFactory::new()->create();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace([
|
||||||
|
'itemName' => $license->name,
|
||||||
|
'serialNumber' => $license->serial
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$probablyNewLicenses = License::query()
|
||||||
|
->where('name', $license->name)
|
||||||
|
->where('serial', $license->serial)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $probablyNewLicenses);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function formatAttributes(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new([
|
||||||
|
'expirationDate' => '2022/10/10'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newLicense = License::query()
|
||||||
|
->where('serial', $importFileBuilder->firstRow()['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newLicense->expiration_date->toDateString(), '2022-10-10');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCompanyWhenCompanyExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['companyName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newLicenses = License::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['company_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newLicenses->pluck('company_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewManufacturerWhenManufacturerExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['manufacturerName' => Str::random()]);
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newLicenses = License::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['manufacturer_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newLicenses->pluck('manufacturer_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewCategoryWhenCategoryExists(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['category' => $this->faker->company]);
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newLicenses = License::query()
|
||||||
|
->whereIn('serial', $importFileBuilder->pluck('serialNumber'))
|
||||||
|
->get(['category_id']);
|
||||||
|
|
||||||
|
$this->assertCount(1, $newLicenses->pluck('category_id')->unique()->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::times()
|
||||||
|
->replace(['name' => ''])
|
||||||
|
->forget(['seats']);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
$row['licenseName'] => [
|
||||||
|
"License \"{$row['licenseName']}\"" => [
|
||||||
|
'seats' => ['The seats field is required.'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newLicenses = License::query()
|
||||||
|
->where('serial', $row['serialNumber'])
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(0, $newLicenses);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateLicenseFromImport(): void
|
||||||
|
{
|
||||||
|
$license = LicenseFactory::new()->create();
|
||||||
|
$importFileBuilder = ImportFileBuilder::new([
|
||||||
|
'licenseName' => $license->name,
|
||||||
|
'serialNumber' => $license->serial
|
||||||
|
]);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedLicense = License::query()
|
||||||
|
->with(['manufacturer', 'category', 'supplier'])
|
||||||
|
->where('serial', $row['serialNumber'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($updatedLicense->name, $row['licenseName']);
|
||||||
|
$this->assertEquals($updatedLicense->serial, $row['serialNumber']);
|
||||||
|
$this->assertEquals($updatedLicense->purchase_date->toDateString(), $row['purchaseDate']);
|
||||||
|
$this->assertEquals($updatedLicense->purchase_cost, $row['purchaseCost']);
|
||||||
|
$this->assertEquals($updatedLicense->order_number, $row['orderNumber']);
|
||||||
|
$this->assertEquals($updatedLicense->seats, $row['seats']);
|
||||||
|
$this->assertEquals($updatedLicense->notes, $row['notes']);
|
||||||
|
$this->assertEquals($updatedLicense->license_name, $row['licensedToName']);
|
||||||
|
$this->assertEquals($updatedLicense->license_email, $row['licensedToEmail']);
|
||||||
|
$this->assertEquals($updatedLicense->supplier->name, $row['supplierName']);
|
||||||
|
$this->assertEquals($updatedLicense->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($updatedLicense->category->name, $row['category']);
|
||||||
|
$this->assertEquals($updatedLicense->expiration_date->toDateString(), $row['expirationDate']);
|
||||||
|
$this->assertEquals($updatedLicense->maintained, $row['isMaintained'] === 'TRUE');
|
||||||
|
$this->assertEquals($updatedLicense->reassignable, $row['isReassignAble'] === 'TRUE');
|
||||||
|
$this->assertEquals($updatedLicense->purchase_order, $license->purchase_order);
|
||||||
|
$this->assertEquals($updatedLicense->depreciation_id, $license->depreciation_id);
|
||||||
|
$this->assertEquals($updatedLicense->termination_date, $license->termination_date);
|
||||||
|
$this->assertEquals($updatedLicense->deprecate, $license->deprecate);
|
||||||
|
$this->assertEquals($updatedLicense->min_amt, $license->min_amt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::times()->definition();
|
||||||
|
$row = [
|
||||||
|
'category' => $faker['supplierName'],
|
||||||
|
'companyName' => $faker['serialNumber'],
|
||||||
|
'expirationDate' => $faker['seats'],
|
||||||
|
'isMaintained' => $faker['purchaseDate'],
|
||||||
|
'isReassignAble' => $faker['purchaseCost'],
|
||||||
|
'licensedToName' => $faker['orderNumber'],
|
||||||
|
'licensedToEmail' => $faker['notes'],
|
||||||
|
'licenseName' => $faker['licenseName'],
|
||||||
|
'manufacturerName' => $faker['category'],
|
||||||
|
'notes' => $faker['companyName'],
|
||||||
|
'orderNumber' => $faker['expirationDate'],
|
||||||
|
'purchaseCost' => $faker['isMaintained'],
|
||||||
|
'purchaseDate' => $faker['isReassignAble'],
|
||||||
|
'seats' => $faker['licensedToName'],
|
||||||
|
'serialNumber' => $faker['licensedToEmail'],
|
||||||
|
'supplierName' => $faker['manufacturerName']
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$import = ImportFactory::new()->license()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Category' => 'supplier',
|
||||||
|
'Company' => 'serial',
|
||||||
|
'expiration date' => 'seats',
|
||||||
|
'maintained' => 'purchase_date',
|
||||||
|
'reassignable' => 'purchase_cost',
|
||||||
|
'Licensed To Name' => 'order_number',
|
||||||
|
'Licensed To Email' => 'notes',
|
||||||
|
'licenseName' => 'name',
|
||||||
|
'manufacturer' => 'category',
|
||||||
|
'Notes' => 'company',
|
||||||
|
'Serial number' => 'license_email',
|
||||||
|
'Order Number' => 'expiration_date',
|
||||||
|
'purchase Cost' => 'maintained',
|
||||||
|
'purchase Date' => 'reassignable',
|
||||||
|
'seats' => 'license_name',
|
||||||
|
'supplier' => 'manufacturer'
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$newLicense = License::query()
|
||||||
|
->with(['category', 'company', 'manufacturer', 'supplier'])
|
||||||
|
->where('serial', $row['companyName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newLicense->name, $row['licenseName']);
|
||||||
|
$this->assertEquals($newLicense->serial, $row['companyName']);
|
||||||
|
$this->assertEquals($newLicense->purchase_date->toDateString(), $row['isMaintained']);
|
||||||
|
$this->assertEquals($newLicense->purchase_cost, $row['isReassignAble']);
|
||||||
|
$this->assertEquals($newLicense->order_number, $row['licensedToName']);
|
||||||
|
$this->assertEquals($newLicense->seats, $row['expirationDate']);
|
||||||
|
$this->assertEquals($newLicense->notes, $row['licensedToEmail']);
|
||||||
|
$this->assertEquals($newLicense->license_name, $row['seats']);
|
||||||
|
$this->assertEquals($newLicense->license_email, $row['serialNumber']);
|
||||||
|
$this->assertEquals($newLicense->supplier->name, $row['category']);
|
||||||
|
$this->assertEquals($newLicense->company->name, $row['notes']);
|
||||||
|
$this->assertEquals($newLicense->category->name, $row['manufacturerName']);
|
||||||
|
$this->assertEquals($newLicense->expiration_date->toDateString(), $row['orderNumber']);
|
||||||
|
$this->assertEquals($newLicense->maintained, $row['purchaseCost'] === 'TRUE');
|
||||||
|
$this->assertEquals($newLicense->reassignable, $row['purchaseDate'] === 'TRUE');
|
||||||
|
$this->assertEquals($newLicense->purchase_order, '');
|
||||||
|
$this->assertNull($newLicense->depreciation_id);
|
||||||
|
$this->assertNull($newLicense->termination_date);
|
||||||
|
$this->assertNull($newLicense->deprecate);
|
||||||
|
$this->assertNull($newLicense->min_amt);
|
||||||
|
}
|
||||||
|
}
|
340
tests/Feature/Importing/Api/ImportUsersTest.php
Normal file
340
tests/Feature/Importing/Api/ImportUsersTest.php
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Importing\Api;
|
||||||
|
|
||||||
|
use App\Models\Location;
|
||||||
|
use App\Models\User;
|
||||||
|
use Database\Factories\AssetFactory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Database\Factories\UserFactory;
|
||||||
|
use Database\Factories\ImportFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\Support\Importing\UsersImportFileBuilder as ImportFileBuilder;
|
||||||
|
|
||||||
|
class ImportUsersTest extends ImportDataTestCase
|
||||||
|
{
|
||||||
|
use WithFaker;
|
||||||
|
|
||||||
|
protected function importFileResponse(array $parameters = []): TestResponse
|
||||||
|
{
|
||||||
|
if (!array_key_exists('import-type', $parameters)) {
|
||||||
|
$parameters['import-type'] = 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::importFileResponse($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
#[DataProvider('permissionsTestData')]
|
||||||
|
public function onlyUserWithPermissionCanImportUsers(array|string $permissions): void
|
||||||
|
{
|
||||||
|
$permissions = collect((array) $permissions)
|
||||||
|
->map(fn (string $permission) => [$permission => '1'])
|
||||||
|
->toJson();
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => 44])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function userWithImportAssetsPermissionCanImportUsers(): void
|
||||||
|
{
|
||||||
|
$this->actingAsForApi(UserFactory::new()->canImport()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->users()->create();
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function importUsers(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$importFileBuilder = ImportFileBuilder::new();
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'send-welcome' => 1])
|
||||||
|
->assertOk()
|
||||||
|
->assertExactJson([
|
||||||
|
'payload' => null,
|
||||||
|
'status' => 'success',
|
||||||
|
'messages' => ['redirect_url' => route('users.index')]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newUser = User::query()
|
||||||
|
->with(['company', 'location'])
|
||||||
|
->where('username', $row['username'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
Notification::assertNothingSent();
|
||||||
|
|
||||||
|
$this->assertEquals($newUser->email, $row['email']);
|
||||||
|
$this->assertEquals($newUser->first_name, $row['firstName']);
|
||||||
|
$this->assertEquals($newUser->last_name, $row['lastName']);
|
||||||
|
$this->assertEquals($newUser->employee_num, $row['employeeNumber']);
|
||||||
|
$this->assertEquals($newUser->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($newUser->location->name, $row['location']);
|
||||||
|
$this->assertEquals($newUser->phone, $row['phoneNumber']);
|
||||||
|
$this->assertEquals($newUser->jobtitle, $row['position']);
|
||||||
|
$this->assertTrue(Hash::isHashed($newUser->password));
|
||||||
|
$this->assertEquals($newUser->website, '');
|
||||||
|
$this->assertEquals($newUser->country, '');
|
||||||
|
$this->assertEquals($newUser->address, '');
|
||||||
|
$this->assertEquals($newUser->city, '');
|
||||||
|
$this->assertEquals($newUser->state, '');
|
||||||
|
$this->assertEquals($newUser->zip, '');
|
||||||
|
$this->assertNull($newUser->permissions);
|
||||||
|
$this->assertNull($newUser->avatar);
|
||||||
|
$this->assertNull($newUser->notes);
|
||||||
|
$this->assertNull($newUser->skin);
|
||||||
|
$this->assertNull($newUser->department_id);
|
||||||
|
$this->assertNull($newUser->two_factor_secret);
|
||||||
|
$this->assertNull($newUser->idap_import);
|
||||||
|
$this->assertEquals($newUser->locale, 'en-US');
|
||||||
|
$this->assertEquals($newUser->show_in_list, 1);
|
||||||
|
$this->assertEquals($newUser->two_factor_enrolled, 0);
|
||||||
|
$this->assertEquals($newUser->two_factor_optin, 0);
|
||||||
|
$this->assertEquals($newUser->remote, 0);
|
||||||
|
$this->assertEquals($newUser->autoassign_licenses, 0);
|
||||||
|
$this->assertEquals($newUser->vip, 0);
|
||||||
|
$this->assertEquals($newUser->enable_sounds, 0);
|
||||||
|
$this->assertEquals($newUser->enable_confetti, 0);
|
||||||
|
$this->assertNull($newUser->created_by);
|
||||||
|
$this->assertNull($newUser->start_date);
|
||||||
|
$this->assertNull($newUser->end_date);
|
||||||
|
$this->assertNull($newUser->scim_externalid);
|
||||||
|
$this->assertNull($newUser->manager_id);
|
||||||
|
$this->assertNull($newUser->activation_code);
|
||||||
|
$this->assertNull($newUser->last_login);
|
||||||
|
$this->assertNull($newUser->persist_code);
|
||||||
|
$this->assertNull($newUser->reset_password_code);
|
||||||
|
$this->assertEquals($newUser->activated, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
|
||||||
|
{
|
||||||
|
$row = ImportFileBuilder::new()->definition();
|
||||||
|
$row['unknownColumnInCsvFile'] = 'foo';
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willNotCreateNewUserWhenUserWithUserNameAlreadyExist(): void
|
||||||
|
{
|
||||||
|
$user = UserFactory::new()->create(['username' => Str::random()]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::times(4)->replace(['username' => $user->username]);
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$probablyNewUsers = User::query()
|
||||||
|
->where('username', $user->username)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(1, $probablyNewUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willGenerateUsernameWhenUsernameFieldIsMissing(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new()->forget('username');
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id])->assertOk();
|
||||||
|
|
||||||
|
$newUser = User::query()
|
||||||
|
->where('email', $row['email'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$generatedUsername = User::generateFormattedNameFromFullName("{$row['firstName']} {$row['lastName']}")['username'];
|
||||||
|
|
||||||
|
$this->assertEquals($newUser->username, $generatedUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function willUpdateLocationOfAllAssetsAssignedToUser(): void
|
||||||
|
{
|
||||||
|
$user = UserFactory::new()->create(['username' => Str::random()]);
|
||||||
|
$assetsAssignedToUser = AssetFactory::new()->create(['assigned_to' => $user->id, 'assigned_type' => User::class]);
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['username' => $user->username]);
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$userLocation = Location::query()->where('name', $importFileBuilder->firstRow()['location'])->sole(['id']);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
$assetsAssignedToUser->refresh()->location_id,
|
||||||
|
$userLocation->id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function whenRequiredColumnsAreMissingInImportFile(): void
|
||||||
|
{
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['firstName' => ''])->forget(['username']);
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse(['import' => $import->id])
|
||||||
|
->assertInternalServerError()
|
||||||
|
->assertExactJson([
|
||||||
|
'status' => 'import-errors',
|
||||||
|
'payload' => null,
|
||||||
|
'messages' => [
|
||||||
|
'' => [
|
||||||
|
'User' => [
|
||||||
|
'first_name' => ['The first name field is required.'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newUsers = User::query()
|
||||||
|
->where('email', $importFileBuilder->firstRow()['email'])
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$this->assertCount(0, $newUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function updateUserFromImport(): void
|
||||||
|
{
|
||||||
|
$user = UserFactory::new()->create(['username' => Str::random()])->refresh();
|
||||||
|
$importFileBuilder = ImportFileBuilder::new(['username' => $user->username]);
|
||||||
|
|
||||||
|
$row = $importFileBuilder->firstRow();
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
$this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();
|
||||||
|
|
||||||
|
$updatedUser = User::query()->with(['company', 'location'])->find($user->id);
|
||||||
|
$updatedAttributes = [
|
||||||
|
'first_name', 'email', 'last_name', 'employee_num', 'company',
|
||||||
|
'location_id', 'company_id', 'updated_at', 'phone', 'jobtitle'
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($updatedUser->email, $row['email']);
|
||||||
|
$this->assertEquals($updatedUser->first_name, $row['firstName']);
|
||||||
|
$this->assertEquals($updatedUser->last_name, $row['lastName']);
|
||||||
|
$this->assertEquals($updatedUser->employee_num, $row['employeeNumber']);
|
||||||
|
$this->assertEquals($updatedUser->company->name, $row['companyName']);
|
||||||
|
$this->assertEquals($updatedUser->location->name, $row['location']);
|
||||||
|
$this->assertEquals($updatedUser->phone, $row['phoneNumber']);
|
||||||
|
$this->assertEquals($updatedUser->jobtitle, $row['position']);
|
||||||
|
$this->assertTrue(Hash::isHashed($updatedUser->password));
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
Arr::except($updatedUser->attributesToArray(), $updatedAttributes),
|
||||||
|
Arr::except($user->attributesToArray(), $updatedAttributes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function customColumnMapping(): void
|
||||||
|
{
|
||||||
|
$faker = ImportFileBuilder::new()->definition();
|
||||||
|
$row = [
|
||||||
|
'companyName' => $faker['username'],
|
||||||
|
'email' => $faker['position'],
|
||||||
|
'employeeNumber' => $faker['phoneNumber'],
|
||||||
|
'firstName' => $faker['location'],
|
||||||
|
'lastName' => $faker['lastName'],
|
||||||
|
'location' => $faker['firstName'],
|
||||||
|
'phoneNumber' => $faker['employeeNumber'],
|
||||||
|
'position' => $faker['email'],
|
||||||
|
'username' => $faker['companyName'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$importFileBuilder = new ImportFileBuilder([$row]);
|
||||||
|
$import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);
|
||||||
|
|
||||||
|
$this->actingAsForApi(UserFactory::new()->superuser()->create());
|
||||||
|
|
||||||
|
$this->importFileResponse([
|
||||||
|
'import' => $import->id,
|
||||||
|
'column-mappings' => [
|
||||||
|
'Company' => 'username',
|
||||||
|
'email' => 'jobtitle',
|
||||||
|
'Employee Number' => 'phone_number',
|
||||||
|
'First Name' => 'location',
|
||||||
|
'Last Name' => 'last_name',
|
||||||
|
'Location' => 'first_name',
|
||||||
|
'Phone Number' => 'employee_num',
|
||||||
|
'Job Title' => 'email',
|
||||||
|
'Username' => 'company',
|
||||||
|
]
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$newUser = User::query()
|
||||||
|
->with(['company', 'location'])
|
||||||
|
->where('username', $row['companyName'])
|
||||||
|
->sole();
|
||||||
|
|
||||||
|
$this->assertEquals($newUser->email, $row['position']);
|
||||||
|
$this->assertEquals($newUser->first_name, $row['location']);
|
||||||
|
$this->assertEquals($newUser->last_name, $row['lastName']);
|
||||||
|
$this->assertEquals($newUser->jobtitle, $row['email']);
|
||||||
|
$this->assertEquals($newUser->employee_num, $row['phoneNumber']);
|
||||||
|
$this->assertEquals($newUser->company->name, $row['username']);
|
||||||
|
$this->assertEquals($newUser->location->name, $row['firstName']);
|
||||||
|
$this->assertEquals($newUser->phone, $row['employeeNumber']);
|
||||||
|
$this->assertTrue(Hash::isHashed($newUser->password));
|
||||||
|
$this->assertEquals($newUser->website, '');
|
||||||
|
$this->assertEquals($newUser->country, '');
|
||||||
|
$this->assertEquals($newUser->address, '');
|
||||||
|
$this->assertEquals($newUser->city, '');
|
||||||
|
$this->assertEquals($newUser->state, '');
|
||||||
|
$this->assertEquals($newUser->zip, '');
|
||||||
|
$this->assertNull($newUser->permissions);
|
||||||
|
$this->assertNull($newUser->avatar);
|
||||||
|
$this->assertNull($newUser->notes);
|
||||||
|
$this->assertNull($newUser->skin);
|
||||||
|
$this->assertNull($newUser->department_id);
|
||||||
|
$this->assertNull($newUser->two_factor_secret);
|
||||||
|
$this->assertNull($newUser->idap_import);
|
||||||
|
$this->assertEquals($newUser->locale, 'en-US');
|
||||||
|
$this->assertEquals($newUser->show_in_list, 1);
|
||||||
|
$this->assertEquals($newUser->two_factor_enrolled, 0);
|
||||||
|
$this->assertEquals($newUser->two_factor_optin, 0);
|
||||||
|
$this->assertEquals($newUser->remote, 0);
|
||||||
|
$this->assertEquals($newUser->autoassign_licenses, 0);
|
||||||
|
$this->assertEquals($newUser->vip, 0);
|
||||||
|
$this->assertEquals($newUser->enable_sounds, 0);
|
||||||
|
$this->assertEquals($newUser->enable_confetti, 0);
|
||||||
|
$this->assertNull($newUser->created_by);
|
||||||
|
$this->assertNull($newUser->start_date);
|
||||||
|
$this->assertNull($newUser->end_date);
|
||||||
|
$this->assertNull($newUser->scim_externalid);
|
||||||
|
$this->assertNull($newUser->manager_id);
|
||||||
|
$this->assertNull($newUser->activation_code);
|
||||||
|
$this->assertNull($newUser->last_login);
|
||||||
|
$this->assertNull($newUser->persist_code);
|
||||||
|
$this->assertNull($newUser->reset_password_code);
|
||||||
|
$this->assertEquals($newUser->activated, 0);
|
||||||
|
}
|
||||||
|
}
|
74
tests/Support/Importing/AccessoriesImportFileBuilder.php
Normal file
74
tests/Support/Importing/AccessoriesImportFileBuilder.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an accessories import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* category?: string,
|
||||||
|
* companyName?: string,
|
||||||
|
* itemName?: string,
|
||||||
|
* location?: string,
|
||||||
|
* manufacturerName?: string,
|
||||||
|
* modelNumber?: string,
|
||||||
|
* notes?: string,
|
||||||
|
* orderNumber?: string,
|
||||||
|
* purchaseCost?: int,
|
||||||
|
* purchaseDate?: string,
|
||||||
|
* quantity?: int,
|
||||||
|
* supplierName?: string
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class AccessoriesImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'category' => 'Category',
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'itemName' => 'Item Name',
|
||||||
|
'location' => 'Location',
|
||||||
|
'manufacturerName' => 'Manufacturer',
|
||||||
|
'modelNumber' => 'Model Number',
|
||||||
|
'notes' => 'Notes',
|
||||||
|
'orderNumber' => 'Order Number',
|
||||||
|
'purchaseCost' => 'Purchase Cost',
|
||||||
|
'purchaseDate' => 'Purchase Date',
|
||||||
|
'quantity' => 'Quantity',
|
||||||
|
'supplierName' => 'Supplier',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'category' => Str::random(),
|
||||||
|
'companyName' => Str::random(),
|
||||||
|
'itemName' => Str::random(),
|
||||||
|
'location' => "{$faker->city}, {$faker->country}",
|
||||||
|
'manufacturerName' => $faker->company,
|
||||||
|
'modelNumber' => Str::random(),
|
||||||
|
'notes' => $faker->sentence,
|
||||||
|
'orderNumber' => Str::random(),
|
||||||
|
'purchaseDate' => $faker->date(),
|
||||||
|
'purchaseCost' => rand(1, 100),
|
||||||
|
'quantity' => rand(1, 100),
|
||||||
|
'supplierName' => $faker->company,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
92
tests/Support/Importing/AssetsImportFileBuilder.php
Normal file
92
tests/Support/Importing/AssetsImportFileBuilder.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an assets import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* assigneeFullName?: string,
|
||||||
|
* assigneeEmail?: string,
|
||||||
|
* assigneeUsername?: string,
|
||||||
|
* category?: string,
|
||||||
|
* companyName?: string,
|
||||||
|
* itemName?: string,
|
||||||
|
* location?: string,
|
||||||
|
* manufacturerName?: int,
|
||||||
|
* model?: string,
|
||||||
|
* modelNumber?: string,
|
||||||
|
* notes?: string,
|
||||||
|
* purchaseCost?: int,
|
||||||
|
* purchaseDate?: string,
|
||||||
|
* serialNumber?: string,
|
||||||
|
* supplierName?: string,
|
||||||
|
* status?: string,
|
||||||
|
* tag?: string,
|
||||||
|
* warrantyInMonths?: int,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class AssetsImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'assigneeFullName' => 'Full Name',
|
||||||
|
'assigneeEmail' => 'Email',
|
||||||
|
'assigneeUsername' => 'Username',
|
||||||
|
'category' => 'Category',
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'itemName' => 'item Name',
|
||||||
|
'location' => 'Location',
|
||||||
|
'manufacturerName' => 'Manufacturer',
|
||||||
|
'model' => 'Model name',
|
||||||
|
'modelNumber' => 'Model Number',
|
||||||
|
'notes' => 'Notes',
|
||||||
|
'purchaseCost' => 'Purchase Cost',
|
||||||
|
'purchaseDate' => 'Purchase Date',
|
||||||
|
'serialNumber' => 'Serial number',
|
||||||
|
'supplierName' => 'Supplier',
|
||||||
|
'status' => 'Status',
|
||||||
|
'tag' => 'Asset Tag',
|
||||||
|
'warrantyInMonths' => 'Warranty',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'assigneeFullName' => $faker->name,
|
||||||
|
'assigneeEmail' => $faker->email,
|
||||||
|
'assigneeUsername' => $faker->userName,
|
||||||
|
'category' => Str::random(),
|
||||||
|
'companyName' => Str::random() . " {$faker->companySuffix}",
|
||||||
|
'itemName' => Str::random(),
|
||||||
|
'location' => "{$faker->country},{$faker->city}",
|
||||||
|
'manufacturerName' => $faker->company,
|
||||||
|
'model' => Str::random(),
|
||||||
|
'modelNumber' => Str::random(),
|
||||||
|
'notes' => $faker->sentence(5),
|
||||||
|
'purchaseCost' => rand(1, 100_000),
|
||||||
|
'purchaseDate' => $faker->date,
|
||||||
|
'serialNumber' => $faker->uuid,
|
||||||
|
'supplierName' => $faker->company,
|
||||||
|
'status' => $faker->randomElement(['Ready to Deploy', 'Archived', 'Pending']),
|
||||||
|
'tag' => Str::random(),
|
||||||
|
'warrantyInMonths' => rand(1, 12),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
65
tests/Support/Importing/ComponentsImportFileBuilder.php
Normal file
65
tests/Support/Importing/ComponentsImportFileBuilder.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a components import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* category?: string,
|
||||||
|
* companyName?: string,
|
||||||
|
* itemName?: string,
|
||||||
|
* location?: string,
|
||||||
|
* orderNumber?: string,
|
||||||
|
* purchaseCost?: int,
|
||||||
|
* purchaseDate?: string,
|
||||||
|
* quantity?: int,
|
||||||
|
* serialNumber?: string,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class ComponentsImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'category' => 'Category',
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'itemName' => 'item Name',
|
||||||
|
'location' => 'Location',
|
||||||
|
'orderNumber' => 'Order Number',
|
||||||
|
'purchaseCost' => 'Purchase Cost',
|
||||||
|
'purchaseDate' => 'Purchase Date',
|
||||||
|
'quantity' => 'Quantity',
|
||||||
|
'serialNumber' => 'Serial number',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'category' => Str::random(),
|
||||||
|
'companyName' => Str::random() . " {$faker->companySuffix}",
|
||||||
|
'itemName' => Str::random(),
|
||||||
|
'location' => "{$faker->city}, {$faker->country}",
|
||||||
|
'orderNumber' => "ON:COM:{$faker->uuid}",
|
||||||
|
'purchaseCost' => rand(1, 100_000),
|
||||||
|
'purchaseDate' => $faker->date,
|
||||||
|
'quantity' => rand(1, 100_000),
|
||||||
|
'serialNumber' => 'SN:COM:' . Str::random(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
65
tests/Support/Importing/ConsumablesImportFileBuilder.php
Normal file
65
tests/Support/Importing/ConsumablesImportFileBuilder.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a consumables import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* category?: string,
|
||||||
|
* companyName?: string,
|
||||||
|
* itemName?: string,
|
||||||
|
* location?: string,
|
||||||
|
* orderNumber?: string,
|
||||||
|
* purchaseCost?: int,
|
||||||
|
* purchaseDate?: string,
|
||||||
|
* quantity?: int,
|
||||||
|
* supplier?: string,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class ConsumablesImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'category' => 'Category',
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'itemName' => 'item Name',
|
||||||
|
'location' => 'Location',
|
||||||
|
'orderNumber' => 'Order Number',
|
||||||
|
'purchaseCost' => 'Purchase Cost',
|
||||||
|
'purchaseDate' => 'Purchase Date',
|
||||||
|
'quantity' => 'Quantity',
|
||||||
|
'supplier' => 'Supplier',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'category' => Str::random(),
|
||||||
|
'companyName' => Str::random() . " {$faker->companySuffix}",
|
||||||
|
'itemName' => Str::random(),
|
||||||
|
'location' => "{$faker->city}, {$faker->country}",
|
||||||
|
'orderNumber' => "ON:CON:{$faker->uuid}",
|
||||||
|
'purchaseCost' => rand(1, 100_000),
|
||||||
|
'purchaseDate' => $faker->date,
|
||||||
|
'quantity' => rand(1, 100_000),
|
||||||
|
'supplier' => Str::random() . " {$faker->companySuffix}",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
249
tests/Support/Importing/FileBuilder.php
Normal file
249
tests/Support/Importing/FileBuilder.php
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use League\Csv\Reader;
|
||||||
|
use OutOfBoundsException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template Row of array
|
||||||
|
*/
|
||||||
|
abstract class FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The import file rows.
|
||||||
|
*
|
||||||
|
* @var Collection<Row>
|
||||||
|
*/
|
||||||
|
protected Collection $rows;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the builders default row.
|
||||||
|
*
|
||||||
|
* @return Row
|
||||||
|
*/
|
||||||
|
abstract public function definition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<Row> $rows
|
||||||
|
*/
|
||||||
|
public function __construct(array $rows = [])
|
||||||
|
{
|
||||||
|
$this->rows = new Collection($rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new file builder instance.
|
||||||
|
*
|
||||||
|
* @param Row $attributes
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function new(array $attributes = [])
|
||||||
|
{
|
||||||
|
$instance = new static;
|
||||||
|
|
||||||
|
return $instance->push($instance->definition())->replace($attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new file builder instance from an import file.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function fromFile(string $filepath)
|
||||||
|
{
|
||||||
|
$instance = new static;
|
||||||
|
|
||||||
|
$reader = Reader::createFromPath($filepath);
|
||||||
|
$importFileHeaders = $reader->first();
|
||||||
|
$dictionary = array_flip($instance->getDictionary());
|
||||||
|
|
||||||
|
foreach ($reader->getRecords() as $key => $record) {
|
||||||
|
$row = [];
|
||||||
|
|
||||||
|
//Skip header.
|
||||||
|
if ($key === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($record as $index => $value) {
|
||||||
|
$columnNameInImportFile = $importFileHeaders[$index];
|
||||||
|
|
||||||
|
//Try to map the value to a dictionary or use the file's
|
||||||
|
//column if the key is not defined in the dictionary.
|
||||||
|
$row[$dictionary[$columnNameInImportFile] ?? $columnNameInImportFile] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance->push($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new builder instance for the given number of rows.
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function times(int $amountOfRows = 1)
|
||||||
|
{
|
||||||
|
$instance = new static;
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $amountOfRows; $i++) {
|
||||||
|
$instance->push($instance->definition());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The the dictionary for mapping row keys to the corresponding import file headers.
|
||||||
|
*
|
||||||
|
* @return array<string,string>
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new row.
|
||||||
|
*
|
||||||
|
* @param Row $row
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function push(array $row)
|
||||||
|
{
|
||||||
|
if (!empty($row)) {
|
||||||
|
$this->rows->push($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pluck an array of values from the rows.
|
||||||
|
*/
|
||||||
|
public function pluck(string $key): array
|
||||||
|
{
|
||||||
|
return $this->rows->pluck($key)->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the keys in each row with the values of the given replacement if they exist.
|
||||||
|
*
|
||||||
|
* @param array<Row> $replacement
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function replace(array $replacement)
|
||||||
|
{
|
||||||
|
$this->rows = $this->rows->map(function (array $row) use ($replacement) {
|
||||||
|
foreach ($replacement as $key => $value) {
|
||||||
|
if (!array_key_exists($key, $row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the the given keys from all rows.
|
||||||
|
*
|
||||||
|
* @param string|array<string> $keys
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function forget(array|string $keys)
|
||||||
|
{
|
||||||
|
$keys = (array) $keys;
|
||||||
|
|
||||||
|
$this->rows = $this->rows->map(function (array $row) use ($keys) {
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
unset($row[$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toCsv(): array
|
||||||
|
{
|
||||||
|
if ($this->rows->isEmpty()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
$rows = $this->rows;
|
||||||
|
$dictionary = $this->getDictionary();
|
||||||
|
|
||||||
|
foreach (array_keys($rows->first()) as $key) {
|
||||||
|
$headers[] = $dictionary[$key] ?? $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows
|
||||||
|
->map(fn (array $row) => array_values(array_combine($headers, $row)))
|
||||||
|
->prepend($headers)
|
||||||
|
->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the rows to the imports folder as a csv file.
|
||||||
|
*
|
||||||
|
* @return string The filename.
|
||||||
|
*/
|
||||||
|
public function saveToImportsDirectory(?string $filename = null): string
|
||||||
|
{
|
||||||
|
$filename ??= Str::random(40) . '.csv';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stream = fopen(config('app.private_uploads') . "/imports/{$filename}", 'w');
|
||||||
|
|
||||||
|
foreach ($this->toCsv() as $row) {
|
||||||
|
fputcsv($stream, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filename;
|
||||||
|
} finally {
|
||||||
|
if (is_resource($stream)) {
|
||||||
|
fclose($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first row of the import file.
|
||||||
|
*
|
||||||
|
* @throws OutOfBoundsException
|
||||||
|
*
|
||||||
|
* @return Row
|
||||||
|
*/
|
||||||
|
public function firstRow(): array
|
||||||
|
{
|
||||||
|
return $this->rows->first(null, fn () => throw new OutOfBoundsException('Could not retrieve row from collection.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the all the rows of the import file.
|
||||||
|
*
|
||||||
|
* @return array<Row>
|
||||||
|
*/
|
||||||
|
public function all(): array
|
||||||
|
{
|
||||||
|
return $this->rows->all();
|
||||||
|
}
|
||||||
|
}
|
86
tests/Support/Importing/LicensesImportFileBuilder.php
Normal file
86
tests/Support/Importing/LicensesImportFileBuilder.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a consumables import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* category?: string,
|
||||||
|
* companyName?: string,
|
||||||
|
* expirationDate?: string,
|
||||||
|
* isMaintained?: bool,
|
||||||
|
* isReassignAble?: bool,
|
||||||
|
* licensedToName?: string,
|
||||||
|
* licensedToEmail?: email,
|
||||||
|
* licenseName?: string,
|
||||||
|
* manufacturerName?: string,
|
||||||
|
* notes?: string,
|
||||||
|
* orderNumber?: string,
|
||||||
|
* purchaseCost?: int,
|
||||||
|
* purchaseDate?: string,
|
||||||
|
* seats?: int,
|
||||||
|
* serialNumber?: string,
|
||||||
|
* supplierName?: string
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class LicensesImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'category' => 'Category',
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'expirationDate' => 'expiration date',
|
||||||
|
'isMaintained' => 'maintained',
|
||||||
|
'isReassignAble' => 'reassignable',
|
||||||
|
'licensedToName' => 'Licensed To Name',
|
||||||
|
'licensedToEmail' => 'Licensed to Email',
|
||||||
|
'licenseName' => 'Item name',
|
||||||
|
'manufacturerName' => 'manufacturer',
|
||||||
|
'notes' => 'notes',
|
||||||
|
'orderNumber' => 'Order Number',
|
||||||
|
'purchaseCost' => 'Purchase Cost',
|
||||||
|
'purchaseDate' => 'Purchase Date',
|
||||||
|
'seats' => 'seats',
|
||||||
|
'serialNumber' => 'Serial number',
|
||||||
|
'supplierName' => 'supplier',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'category' => Str::random(),
|
||||||
|
'companyName' => Str::random() . " {$faker->companySuffix}",
|
||||||
|
'expirationDate' => $faker->date,
|
||||||
|
'isMaintained' => $faker->randomElement(['TRUE', 'FALSE']),
|
||||||
|
'isReassignAble' => $faker->randomElement(['TRUE', 'FALSE']),
|
||||||
|
'licensedToName' => $faker->name,
|
||||||
|
'licensedToEmail' => $faker->email,
|
||||||
|
'licenseName' => $faker->company,
|
||||||
|
'manufacturerName' => $faker->company,
|
||||||
|
'notes' => $faker->sentence,
|
||||||
|
'orderNumber' => "ON:LIC:{$faker->uuid}",
|
||||||
|
'purchaseCost' => rand(1, 100_000),
|
||||||
|
'purchaseDate' => $faker->date,
|
||||||
|
'seats' => rand(1, 10),
|
||||||
|
'serialNumber' => 'SN:LIC:' . Str::random(),
|
||||||
|
'supplierName' => $faker->company,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
65
tests/Support/Importing/UsersImportFileBuilder.php
Normal file
65
tests/Support/Importing/UsersImportFileBuilder.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Support\Importing;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a users import file at runtime for testing.
|
||||||
|
*
|
||||||
|
* @template Row of array{
|
||||||
|
* companyName?: string,
|
||||||
|
* email?: string,
|
||||||
|
* employeeNumber?: int,
|
||||||
|
* firstName?: string,
|
||||||
|
* lastName?: string,
|
||||||
|
* location?: string,
|
||||||
|
* phoneNumber?: string,
|
||||||
|
* position?: string,
|
||||||
|
* username?: string,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @extends FileBuilder<Row>
|
||||||
|
*/
|
||||||
|
class UsersImportFileBuilder extends FileBuilder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected function getDictionary(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'companyName' => 'Company',
|
||||||
|
'email' => 'email',
|
||||||
|
'employeeNumber' => 'Employee Number',
|
||||||
|
'firstName' => 'First Name',
|
||||||
|
'lastName' => 'Last Name',
|
||||||
|
'location' => 'Location',
|
||||||
|
'phoneNumber' => 'Phone Number',
|
||||||
|
'position' => 'Job Title',
|
||||||
|
'username' => 'Username',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$faker = fake();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'companyName' => $faker->company,
|
||||||
|
'email' => Str::random(32) . "@{$faker->freeEmailDomain}",
|
||||||
|
'employeeNumber' => $faker->uuid,
|
||||||
|
'firstName' => $faker->firstName,
|
||||||
|
'lastName' => $faker->lastName,
|
||||||
|
'location' => "{$faker->city}, {$faker->country}",
|
||||||
|
'phoneNumber' => $faker->phoneNumber,
|
||||||
|
'position' => $faker->jobTitle,
|
||||||
|
'username' => Str::random(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue