mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-24 06:10:01 +00:00
266 lines
9.7 KiB
PHP
266 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Assets\Ui;
|
|
|
|
use App\Models\Actionlog;
|
|
use App\Models\Asset;
|
|
use App\Models\Import;
|
|
use App\Models\User;
|
|
use Tests\Support\Importing\AssetHistoryImportFileBuilder;
|
|
use Tests\Support\Importing\CleansUpImportFiles;
|
|
use Tests\TestCase;
|
|
|
|
class ImportAssetHistoryTest extends TestCase
|
|
{
|
|
use CleansUpImportFiles;
|
|
|
|
public function test_legacy_get_history_route_redirects_to_importer(): void
|
|
{
|
|
$this->actingAs(User::factory()->create())
|
|
->get('/hardware/history')
|
|
->assertRedirect(route('imports.index'));
|
|
}
|
|
|
|
public function test_legacy_post_history_endpoint_is_gone(): void
|
|
{
|
|
$this->actingAs(User::factory()->admin()->create())
|
|
->post('/hardware/history')
|
|
->assertStatus(405);
|
|
}
|
|
|
|
public function test_process_endpoint_blocked_in_demo_mode_for_non_superadmin(): void
|
|
{
|
|
// Uploads are blocked at Api\ImportController::store, and non-
|
|
// superadmins are also blocked from processing so they can't
|
|
// mutate the demo DB via any leftover Import row. Superadmins
|
|
// are allowed through so they can exercise the seeded demo
|
|
// samples end to end (see companion test below).
|
|
config(['app.lock_passwords' => true]);
|
|
|
|
$actor = User::factory()->canImport()->create();
|
|
$import = Import::factory()->assetHistory()->create(['created_by' => $actor->id]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
['import-type' => 'assetHistory', 'import' => $import->id],
|
|
)->assertStatus(422);
|
|
}
|
|
|
|
public function test_process_endpoint_allowed_in_demo_mode_for_superadmin(): void
|
|
{
|
|
// Superadmins bypass the demo-mode gate on process() so the
|
|
// seeded sample CSVs (populated by snipeit:demo-settings) can
|
|
// actually be run against the demo DB. Without a real CSV on
|
|
// disk the import will error out below the gate, so this test
|
|
// just proves the gate itself lets the superadmin through
|
|
// (any status other than 422 "feature disabled" is fine).
|
|
config(['app.lock_passwords' => true]);
|
|
|
|
$actor = User::factory()->canImport()->superuser()->create();
|
|
$import = Import::factory()->assetHistory()->create(['created_by' => $actor->id]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$response = $this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
['import-type' => 'assetHistory', 'import' => $import->id],
|
|
);
|
|
|
|
$this->assertNotEquals(
|
|
trans('general.feature_disabled'),
|
|
$response->json('messages'),
|
|
'Superadmin should not hit the demo-mode gate on process().',
|
|
);
|
|
}
|
|
|
|
public function test_asset_history_import_requires_import_permission(): void
|
|
{
|
|
$actor = User::factory()->create();
|
|
$import = Import::factory()->assetHistory()->create(['created_by' => $actor->id]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
['import-type' => 'assetHistory', 'import' => $import->id],
|
|
)->assertForbidden();
|
|
}
|
|
|
|
public function test_asset_history_import_creates_actionlogs_and_assigns_user(): void
|
|
{
|
|
$actor = User::factory()->canImport()->create();
|
|
$target = User::factory()->create(['username' => 'target.user']);
|
|
$asset = Asset::factory()->create([
|
|
'asset_tag' => 'AHIST-1',
|
|
'assigned_to' => null,
|
|
'assigned_type' => null,
|
|
]);
|
|
|
|
$checkoutDate = now()->subDay()->format('Y-m-d H:i:s');
|
|
$checkinDate = now()->addDays(30)->format('Y-m-d H:i:s');
|
|
|
|
$file = AssetHistoryImportFileBuilder::new([
|
|
'assetTag' => $asset->asset_tag,
|
|
'name' => $target->username,
|
|
'email' => '',
|
|
'checkoutDate' => $checkoutDate,
|
|
'checkinDate' => $checkinDate,
|
|
]);
|
|
|
|
$import = Import::factory()->assetHistory()->create([
|
|
'created_by' => $actor->id,
|
|
'file_path' => $file->saveToImportsDirectory(),
|
|
]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
[
|
|
'import-type' => 'assetHistory',
|
|
'import' => $import->id,
|
|
'match_username' => true,
|
|
],
|
|
)->assertOk();
|
|
|
|
$asset->refresh();
|
|
$this->assertEquals($target->id, $asset->assigned_to);
|
|
$this->assertEquals(User::class, $asset->assigned_type);
|
|
|
|
$this->assertDatabaseHas('action_logs', [
|
|
'item_id' => $asset->id,
|
|
'item_type' => Asset::class,
|
|
'target_id' => $target->id,
|
|
'target_type' => User::class,
|
|
'action_type' => 'checkout',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('action_logs', [
|
|
'item_id' => $asset->id,
|
|
'item_type' => Asset::class,
|
|
'target_id' => null,
|
|
'action_type' => 'checkin',
|
|
]);
|
|
}
|
|
|
|
public function test_asset_history_import_does_not_reassign_when_checkin_is_past(): void
|
|
{
|
|
$actor = User::factory()->canImport()->create();
|
|
$target = User::factory()->create(['username' => 'past.user']);
|
|
$asset = Asset::factory()->create([
|
|
'asset_tag' => 'AHIST-2',
|
|
'assigned_to' => null,
|
|
'assigned_type' => null,
|
|
]);
|
|
|
|
$file = AssetHistoryImportFileBuilder::new([
|
|
'assetTag' => $asset->asset_tag,
|
|
'name' => $target->username,
|
|
'email' => '',
|
|
'checkoutDate' => now()->subDays(30)->format('Y-m-d H:i:s'),
|
|
'checkinDate' => now()->subDays(15)->format('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
$import = Import::factory()->assetHistory()->create([
|
|
'created_by' => $actor->id,
|
|
'file_path' => $file->saveToImportsDirectory(),
|
|
]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
[
|
|
'import-type' => 'assetHistory',
|
|
'import' => $import->id,
|
|
'match_username' => true,
|
|
],
|
|
)->assertOk();
|
|
|
|
$asset->refresh();
|
|
$this->assertNull($asset->assigned_to);
|
|
$this->assertNull($asset->assigned_type);
|
|
|
|
// Historical checkout + checkin actionlogs both got written even
|
|
// though the asset ends up in a checked-in state.
|
|
$this->assertDatabaseHas('action_logs', [
|
|
'item_id' => $asset->id,
|
|
'action_type' => 'checkout',
|
|
]);
|
|
$this->assertDatabaseHas('action_logs', [
|
|
'item_id' => $asset->id,
|
|
'action_type' => 'checkin',
|
|
]);
|
|
}
|
|
|
|
public function test_asset_history_import_skips_unknown_asset_tag(): void
|
|
{
|
|
$actor = User::factory()->canImport()->create();
|
|
User::factory()->create(['username' => 'someone']);
|
|
|
|
$file = AssetHistoryImportFileBuilder::new([
|
|
'assetTag' => 'DOES-NOT-EXIST',
|
|
'name' => 'someone',
|
|
'email' => '',
|
|
'checkoutDate' => now()->format('Y-m-d H:i:s'),
|
|
'checkinDate' => '',
|
|
]);
|
|
|
|
$import = Import::factory()->assetHistory()->create([
|
|
'created_by' => $actor->id,
|
|
'file_path' => $file->saveToImportsDirectory(),
|
|
]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
// Skipped rows now surface as import-errors so the wizard can
|
|
// display the reason (Asset does not exist) instead of hiding it
|
|
// in laravel.log. The 500 status is the shared API contract for
|
|
// an errors-non-empty return.
|
|
$response = $this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
['import-type' => 'assetHistory', 'import' => $import->id, 'match_username' => true],
|
|
);
|
|
$response->assertStatus(500);
|
|
$this->assertEquals('import-errors', $response->json('status'));
|
|
$this->assertArrayHasKey('Asset DOES-NOT-EXIST', $response->json('messages'));
|
|
|
|
$this->assertSame(0, Actionlog::where('note', 'like', '%history importer%')->count());
|
|
}
|
|
|
|
public function test_asset_history_import_skips_row_when_user_not_matched(): void
|
|
{
|
|
$actor = User::factory()->canImport()->create();
|
|
$asset = Asset::factory()->create([
|
|
'asset_tag' => 'AHIST-3',
|
|
'assigned_to' => null,
|
|
'assigned_type' => null,
|
|
]);
|
|
|
|
$file = AssetHistoryImportFileBuilder::new([
|
|
'assetTag' => $asset->asset_tag,
|
|
'name' => 'no.such.user',
|
|
'email' => '',
|
|
'checkoutDate' => now()->format('Y-m-d H:i:s'),
|
|
'checkinDate' => '',
|
|
]);
|
|
|
|
$import = Import::factory()->assetHistory()->create([
|
|
'created_by' => $actor->id,
|
|
'file_path' => $file->saveToImportsDirectory(),
|
|
]);
|
|
|
|
$this->actingAsForApi($actor);
|
|
$response = $this->postJson(
|
|
route('api.imports.importFile', ['import' => $import->id]),
|
|
['import-type' => 'assetHistory', 'import' => $import->id, 'match_username' => true],
|
|
);
|
|
$response->assertStatus(500);
|
|
$this->assertEquals('import-errors', $response->json('status'));
|
|
$this->assertArrayHasKey('Asset '.$asset->asset_tag, $response->json('messages'));
|
|
|
|
$asset->refresh();
|
|
$this->assertNull($asset->assigned_to);
|
|
$this->assertDatabaseMissing('action_logs', [
|
|
'item_id' => $asset->id,
|
|
'action_type' => 'checkout',
|
|
]);
|
|
}
|
|
}
|