3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-22 21:29:23 +00:00
Files
snipe-it/tests/Feature/Importing/AssetImportCheckoutAcceptanceTest.php

158 lines
5.8 KiB
PHP

<?php
namespace Tests\Feature\Importing;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
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;
/**
* Pin the "CSV import creates a checkout_acceptance row when the imported
* asset requires acceptance and is being checked out to a user" behavior.
*
* The import path routes through Asset::checkOut which fires
* CheckoutableCheckedOut. CheckoutableListener::handleAcceptance responds
* by calling CreateCheckoutAcceptanceAction::run when the target is a
* User and the checkoutable's category requires acceptance. This is the
* same code path the UI checkout uses. No importer-specific gate exists.
* Without this regression test a future refactor of the listener or the
* importer could silently drop acceptance creation on imported checkouts,
* leaving imported assets in an unaccepted-but-checked-out state that
* would only surface later in the acceptance-alerting cron.
*/
class AssetImportCheckoutAcceptanceTest extends TestCase
{
private 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);
}
#[Test]
public function importing_a_user_checkout_creates_an_acceptance_record_when_category_requires_it(): void
{
$category = Category::factory()->create([
'category_type' => 'asset',
'require_acceptance' => true,
]);
Location::factory()->create();
$statusLabel = Statuslabel::factory()->create();
Company::factory()->create();
$assetModel = AssetModel::factory()->for($category, 'category')->create(['name' => 'Acceptance Test Model']);
$target = User::factory()->create(['username' => 'accepting_user']);
$importer = User::factory()->canImport()->create();
Asset::factory()
->for($assetModel, 'model')
->for($statusLabel, 'status')
->create(['asset_tag' => 'ACCEPT-001', 'assigned_to' => null]);
$csv = "asset tag,checkout user\n";
$csv .= "ACCEPT-001,{$target->username}\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('acceptance.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',
'checkout user' => 'checkout_user',
],
])
->assertSuccessful();
$imported = Asset::where('asset_tag', 'ACCEPT-001')->firstOrFail();
$this->assertSame($target->id, $imported->assigned_to);
$this->assertDatabaseHas('checkout_acceptances', [
'checkoutable_type' => Asset::class,
'checkoutable_id' => $imported->id,
'assigned_to_id' => $target->id,
'accepted_at' => null,
'declined_at' => null,
]);
}
#[Test]
public function importing_a_location_checkout_does_not_create_an_acceptance_record(): void
{
// Locations can't "accept" anything; the listener guards on
// target instanceof User. Pinning this so a future change to the
// guard doesn't accidentally start creating orphan acceptance
// rows for location checkouts.
$category = Category::factory()->create([
'category_type' => 'asset',
'require_acceptance' => true,
]);
$statusLabel = Statuslabel::factory()->create();
Company::factory()->create();
$assetModel = AssetModel::factory()->for($category, 'category')->create();
$destination = Location::factory()->create(['name' => 'Storage Room B']);
$importer = User::factory()->canImport()->create();
Asset::factory()
->for($assetModel, 'model')
->for($statusLabel, 'status')
->create(['asset_tag' => 'ACCEPT-002', 'assigned_to' => null]);
$csv = "asset tag,checkout location\n";
$csv .= "ACCEPT-002,{$destination->name}\n";
$this->actingAsForApi($importer)
->postJson(route('api.imports.store'), [
'files' => [
$this->createFakeUploadedFile('acceptance-location.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',
'checkout location' => 'checkout_location',
],
])
->assertSuccessful();
$imported = Asset::where('asset_tag', 'ACCEPT-002')->firstOrFail();
$this->assertSame($destination->id, $imported->assigned_to);
$this->assertSame(Location::class, $imported->assigned_type);
$this->assertSame(
0,
CheckoutAcceptance::where('checkoutable_type', Asset::class)
->where('checkoutable_id', $imported->id)
->count(),
'A location checkout must not create an acceptance record.',
);
}
}