3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00
Files
snipe-it/tests/Feature/Importing/AssetImportCreatedByTest.php
2026-07-29 16:25:50 +01:00

260 lines
10 KiB
PHP

<?php
namespace Tests\Feature\Importing;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Company;
use App\Models\Department;
use App\Models\Import;
use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* Regression coverage for the "assets imported via CSV have blank created_by"
* bug. ItemImporter::handle() populated $this->item['created_by'] with
* auth()->id(), but sanitizeItemForStoring() then filtered the payload
* through $model->getFillable(), which does not include created_by on the
* Asset model. Every other importer (Category, Location, License, Supplier,
* User, etc.) worked around this by setting $model->created_by = auth()->id()
* directly on the model. Asset was the odd one out, and every asset created
* via the importer ended up with a null created_by column.
*/
class AssetImportCreatedByTest extends TestCase
{
#[Test]
public function new_asset_created_via_import_records_the_importing_users_id_as_created_by(): void
{
$category = Category::factory()->create(['category_type' => 'asset']);
Location::factory()->create();
$statusLabel = Statuslabel::factory()->create();
Company::factory()->create();
AssetModel::factory()->for($category, 'category')->create(['name' => 'Import Test Model']);
$importer = User::factory()->canImport()->create();
$csv = "asset tag,item name,category,status,model name\n";
$csv .= "CREATEDBY-001,Import Created Asset,{$category->name},{$statusLabel->name},Import Test Model\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('createdby.csv', $csv),
],
])
->assertSuccessful();
$import = Import::latest()->first();
$this->actingAsForApi($importer)
->postJson(route('api.imports.importFile', $import->id), [
'import-type' => 'asset',
'column-mappings' => [
'asset tag' => 'asset_tag',
'item name' => 'item_name',
'category' => 'category',
'status' => 'status',
'model name' => 'asset_model',
],
])
->assertSuccessful();
$imported = Asset::where('asset_tag', 'CREATEDBY-001')->firstOrFail();
$this->assertSame(
$importer->id,
$imported->created_by,
'AssetImporter must set created_by to the id of the user running the import.'
);
}
#[Test]
public function updating_an_existing_asset_via_import_does_not_overwrite_created_by(): void
{
// Update path uses sanitizeItemForStoring(updating: true) which rejects
// empty values, so a null created_by from the item array cannot silently
// clobber the existing value. Pin the intended non-behavior explicitly
// so a future refactor cannot introduce a regression that would
// reassign every updated asset's created_by to the importer.
$category = Category::factory()->create(['category_type' => 'asset']);
$location = Location::factory()->create();
$statusLabel = Statuslabel::factory()->create();
$company = Company::factory()->create();
$assetModel = AssetModel::factory()->for($category, 'category')->create();
$originalOwner = User::factory()->create();
$asset = Asset::factory()
->for($assetModel, 'model')
->for($statusLabel, 'status')
->for($location, 'defaultLoc')
->for($company, 'company')
->create([
'asset_tag' => 'CREATEDBY-002',
'name' => 'Existing Asset',
'created_by' => $originalOwner->id,
]);
$importer = User::factory()->canImport()->create();
$csv = "asset tag,item name,category,status\n";
$csv .= "CREATEDBY-002,Renamed Via Import,{$category->name},{$statusLabel->name}\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('createdby-update.csv', $csv),
],
])
->assertSuccessful();
$import = Import::latest()->first();
$this->actingAsForApi($importer)
->postJson(route('api.imports.importFile', $import->id), [
'import-type' => 'asset',
'import-update' => true,
'column-mappings' => [
'asset tag' => 'asset_tag',
'item name' => 'item_name',
'category' => 'category',
'status' => 'status',
],
])
->assertSuccessful();
$asset->refresh();
$this->assertSame(
$originalOwner->id,
$asset->created_by,
'Import updates must not overwrite the existing created_by on an existing asset.'
);
$this->assertSame('Renamed Via Import', $asset->name, 'Sanity check: the update should still take effect on other fields.');
}
#[Test]
public function auto_created_checkout_target_user_records_created_by(): void
{
// Snipe-IT auto-creates the target user when an asset import row
// references a username that doesn't exist yet. Before the fix,
// that new user landed with created_by = null, matching the same
// omission on the asset itself.
$category = Category::factory()->create(['category_type' => 'asset']);
Location::factory()->create();
$statusLabel = Statuslabel::factory()->create();
Company::factory()->create();
AssetModel::factory()->for($category, 'category')->create(['name' => 'Auto User Test Model']);
$importer = User::factory()->canImport()->create();
$csv = "asset tag,item name,category,status,model name,full name,username\n";
$csv .= "CREATEDBY-003,Import Asset Checkout,{$category->name},{$statusLabel->name},Auto User Test Model,Import Target User,import_target_user\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('createdby-user.csv', $csv),
],
])
->assertSuccessful();
$import = Import::latest()->first();
$this->actingAsForApi($importer)
->postJson(route('api.imports.importFile', $import->id), [
'import-type' => 'asset',
'column-mappings' => [
'asset tag' => 'asset_tag',
'item name' => 'item_name',
'category' => 'category',
'status' => 'status',
'model name' => 'asset_model',
'full name' => 'full_name',
'username' => 'username',
],
])
->assertSuccessful();
$autoCreatedUser = User::where('username', 'import_target_user')->firstOrFail();
$this->assertSame(
$importer->id,
$autoCreatedUser->created_by,
'A user auto-created as the checkout target during an asset import must record the importing user as its creator.'
);
}
#[Test]
public function auto_created_department_records_created_by_and_attaches_to_the_auto_created_user(): void
{
// Two side-effects tied together: the department gets created
// (previously with null created_by), and its id gets propagated
// through to the auto-created user (previously dropped on the
// floor because createOrFetchUser hard-coded department_id to '').
$category = Category::factory()->create(['category_type' => 'asset']);
Location::factory()->create();
$statusLabel = Statuslabel::factory()->create();
Company::factory()->create();
AssetModel::factory()->for($category, 'category')->create(['name' => 'Auto Dept Test Model']);
$importer = User::factory()->canImport()->create();
$csv = "asset tag,item name,category,status,model name,full name,username,department\n";
$csv .= "CREATEDBY-004,Import Dept Asset,{$category->name},{$statusLabel->name},Auto Dept Test Model,Dept Target User,dept_target_user,Import Test Department\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('createdby-dept.csv', $csv),
],
])
->assertSuccessful();
$import = Import::latest()->first();
$this->actingAsForApi($importer)
->postJson(route('api.imports.importFile', $import->id), [
'import-type' => 'asset',
'column-mappings' => [
'asset tag' => 'asset_tag',
'item name' => 'item_name',
'category' => 'category',
'status' => 'status',
'model name' => 'asset_model',
'full name' => 'full_name',
'username' => 'username',
'department' => 'department',
],
])
->assertSuccessful();
$autoCreatedDept = Department::where('name', 'Import Test Department')->firstOrFail();
$this->assertSame(
$importer->id,
$autoCreatedDept->created_by,
'A department auto-created during asset import must record the importing user as its creator.'
);
$autoCreatedUser = User::where('username', 'dept_target_user')->firstOrFail();
$this->assertSame(
$autoCreatedDept->id,
$autoCreatedUser->department_id,
'The department created for the CSV row must be linked to the auto-created checkout-target user (department propagation).'
);
}
protected function createFakeUploadedFile(string $filename, string $content): UploadedFile
{
$path = tempnam(sys_get_temp_dir(), 'csv');
file_put_contents($path, $content);
return new UploadedFile($path, $filename, 'text/csv', null, true);
}
}