This commit is contained in:
snipe
2026-04-20 13:30:22 +01:00
parent baee6a37ea
commit 42cd5e0017
4 changed files with 118 additions and 118 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ class Actionlog extends SnipeModel
// Only resolve company_id if it was never explicitly set by the caller.
// Using array_key_exists on getRawOriginal() / getAttributes() lets us
// distinguish "was set to null intentionally" from "was never set at all".
if (!array_key_exists('company_id', $actionlog->getAttributes())) {
if (! array_key_exists('company_id', $actionlog->getAttributes())) {
$actionlog->company_id = static::resolveCompanyIdFromAttributes(
$actionlog->target_type,
$actionlog->target_id,
@@ -16,6 +16,7 @@ use Illuminate\Support\Facades\DB;
return new class extends Migration
{
private const ASSET_CLASS = 'App\\Models\\Asset';
private const AUDIT_ACTION = 'audit';
public function up(): void
@@ -36,7 +37,7 @@ return new class extends Migration
{
if ($driver === 'mysql' || $driver === 'mariadb') {
// MySQL/MariaDB supports UPDATE ... JOIN directly
DB::statement("
DB::statement('
UPDATE action_logs al
INNER JOIN assets src
ON src.id = al.item_id
@@ -46,10 +47,10 @@ return new class extends Migration
AND al.item_type = ?
AND al.company_id IS NULL
AND al.deleted_at IS NULL
", [self::AUDIT_ACTION, self::ASSET_CLASS]);
', [self::AUDIT_ACTION, self::ASSET_CLASS]);
} else {
// SQLite / PostgreSQL: use a correlated subquery update
DB::statement("
DB::statement('
UPDATE action_logs
SET company_id = (
SELECT src.company_id
@@ -67,8 +68,7 @@ return new class extends Migration
WHERE src2.id = action_logs.item_id
AND src2.company_id IS NOT NULL
)
", [self::AUDIT_ACTION, self::ASSET_CLASS]);
', [self::AUDIT_ACTION, self::ASSET_CLASS]);
}
}
};
@@ -18,6 +18,7 @@ use Tests\TestCase;
class ActionlogCompanyIdBackfillTest extends TestCase
{
private const ASSET_CLASS = 'App\\Models\\Asset';
private const AUDIT_ACTION = 'audit';
/**
@@ -28,11 +29,11 @@ class ActionlogCompanyIdBackfillTest extends TestCase
{
return DB::table('action_logs')->insertGetId(array_merge([
'action_type' => self::AUDIT_ACTION,
'item_type' => self::ASSET_CLASS,
'item_id' => null,
'company_id' => null,
'created_at' => now(),
'updated_at' => now(),
'item_type' => self::ASSET_CLASS,
'item_id' => null,
'company_id' => null,
'created_at' => now(),
'updated_at' => now(),
], $attributes));
}
@@ -44,7 +45,7 @@ class ActionlogCompanyIdBackfillTest extends TestCase
$driver = DB::getDriverName();
if ($driver === 'mysql' || $driver === 'mariadb') {
DB::statement("
DB::statement('
UPDATE action_logs al
INNER JOIN assets src ON src.id = al.item_id AND src.company_id IS NOT NULL
SET al.company_id = src.company_id
@@ -52,9 +53,9 @@ class ActionlogCompanyIdBackfillTest extends TestCase
AND al.item_type = ?
AND al.company_id IS NULL
AND al.deleted_at IS NULL
", [self::AUDIT_ACTION, self::ASSET_CLASS]);
', [self::AUDIT_ACTION, self::ASSET_CLASS]);
} else {
DB::statement("
DB::statement('
UPDATE action_logs
SET company_id = (
SELECT src.company_id FROM assets src
@@ -69,7 +70,7 @@ class ActionlogCompanyIdBackfillTest extends TestCase
SELECT 1 FROM assets src2
WHERE src2.id = action_logs.item_id AND src2.company_id IS NOT NULL
)
", [self::AUDIT_ACTION, self::ASSET_CLASS]);
', [self::AUDIT_ACTION, self::ASSET_CLASS]);
}
}
@@ -78,35 +79,35 @@ class ActionlogCompanyIdBackfillTest extends TestCase
public function test_backfill_populates_company_id_for_asset_audit(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$asset = Asset::factory()->create(['company_id' => $company->id]);
$logId = $this->insertLegacyLog(['item_type' => self::ASSET_CLASS, 'item_id' => $asset->id]);
$this->runBackfill();
$this->assertDatabaseHas('action_logs', [
'id' => $logId,
'id' => $logId,
'company_id' => $company->id,
]);
}
public function test_backfill_does_not_overwrite_existing_company_id(): void
{
$company = Company::factory()->create();
$otherCompany = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $otherCompany->id]);
$company = Company::factory()->create();
$otherCompany = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $otherCompany->id]);
// Row already has a company_id — the backfill must leave it alone
$logId = $this->insertLegacyLog([
'item_type' => self::ASSET_CLASS,
'item_id' => $asset->id,
'item_type' => self::ASSET_CLASS,
'item_id' => $asset->id,
'company_id' => $company->id,
]);
$this->runBackfill();
$this->assertDatabaseHas('action_logs', [
'id' => $logId,
'id' => $logId,
'company_id' => $company->id, // unchanged
]);
}
@@ -120,7 +121,7 @@ class ActionlogCompanyIdBackfillTest extends TestCase
$this->runBackfill();
$this->assertDatabaseHas('action_logs', [
'id' => $logId,
'id' => $logId,
'company_id' => null, // item has no company, so log stays null
]);
}
@@ -128,20 +129,19 @@ class ActionlogCompanyIdBackfillTest extends TestCase
public function test_backfill_ignores_non_audit_action_logs(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$asset = Asset::factory()->create(['company_id' => $company->id]);
$logId = $this->insertLegacyLog([
'action_type' => 'checkout',
'item_type' => self::ASSET_CLASS,
'item_id' => $asset->id,
'item_type' => self::ASSET_CLASS,
'item_id' => $asset->id,
]);
$this->runBackfill();
$this->assertDatabaseHas('action_logs', [
'id' => $logId,
'id' => $logId,
'company_id' => null,
]);
}
}
@@ -3,14 +3,15 @@
namespace Tests\Feature\ActionLogs;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\User;
use Tests\TestCase;
@@ -31,16 +32,16 @@ class ActionlogCompanyIdTest extends TestCase
public function test_asset_audit_log_stores_the_assets_company_id(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.asset.audit', $asset), ['note' => 'audit test'])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'audit',
'company_id' => $company->id,
]);
@@ -49,80 +50,80 @@ class ActionlogCompanyIdTest extends TestCase
public function test_asset_checkout_to_user_log_stores_the_assets_company_id(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => 'user',
'assigned_user' => $user->id,
'status_id' => $asset->status_id,
'assigned_user' => $user->id,
'status_id' => $asset->status_id,
])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
public function test_asset_checkout_to_location_log_stores_the_assets_company_id(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$company = Company::factory()->create();
$asset = Asset::factory()->create(['company_id' => $company->id]);
$location = Location::factory()->create();
$admin = User::factory()->superuser()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => 'location',
'assigned_location' => $location->id,
'status_id' => $asset->status_id,
'status_id' => $asset->status_id,
])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
public function test_asset_checkin_log_stores_the_assets_company_id(): void
{
$company = Company::factory()->create();
$asset = Asset::factory()->assignedToUser()->create(['company_id' => $company->id]);
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->assignedToUser()->create(['company_id' => $company->id]);
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.asset.checkin', $asset))
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'checkin from',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
public function test_asset_create_log_stores_the_assets_company_id(): void
{
$company = Company::factory()->create();
$admin = User::factory()->superuser()->create();
$model = \App\Models\AssetModel::factory()->create();
$status = \App\Models\Statuslabel::factory()->readyToDeploy()->create();
$tag = 'COMPANY-ID-TEST-' . uniqid();
$admin = User::factory()->superuser()->create();
$model = AssetModel::factory()->create();
$status = Statuslabel::factory()->readyToDeploy()->create();
$tag = 'COMPANY-ID-TEST-'.uniqid();
$this->actingAsForApi($admin)
->postJson(route('api.assets.store'), [
'asset_tag' => $tag,
'model_id' => $model->id,
'status_id' => $status->id,
'asset_tag' => $tag,
'model_id' => $model->id,
'status_id' => $status->id,
'company_id' => $company->id,
])
->assertStatusMessageIs('success');
@@ -130,10 +131,10 @@ class ActionlogCompanyIdTest extends TestCase
$asset = Asset::where('asset_tag', $tag)->firstOrFail();
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'create',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -143,31 +144,31 @@ class ActionlogCompanyIdTest extends TestCase
public function test_accessory_checkout_log_stores_the_accessorys_company_id(): void
{
$company = Company::factory()->create();
$company = Company::factory()->create();
$accessory = Accessory::factory()->create(['company_id' => $company->id]);
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_user' => $user->id,
'assigned_user' => $user->id,
'checkout_to_type' => 'user',
])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Accessory::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'item_id' => $accessory->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
public function test_accessory_checkin_log_stores_the_accessorys_company_id(): void
{
$company = Company::factory()->create();
$company = Company::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser()->create(['company_id' => $company->id]);
$admin = User::factory()->superuser()->create();
$admin = User::factory()->superuser()->create();
$checkoutRecord = $accessory->checkouts->first();
@@ -176,10 +177,10 @@ class ActionlogCompanyIdTest extends TestCase
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Accessory::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'item_id' => $accessory->id,
'action_type' => 'checkin from',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -189,10 +190,10 @@ class ActionlogCompanyIdTest extends TestCase
public function test_consumable_checkout_log_stores_the_consumables_company_id(): void
{
$company = Company::factory()->create();
$company = Company::factory()->create();
$consumable = Consumable::factory()->create(['company_id' => $company->id]);
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.consumables.checkout', $consumable), [
@@ -201,10 +202,10 @@ class ActionlogCompanyIdTest extends TestCase
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Consumable::class,
'item_id' => $consumable->id,
'item_type' => Consumable::class,
'item_id' => $consumable->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -214,37 +215,37 @@ class ActionlogCompanyIdTest extends TestCase
public function test_component_checkout_log_stores_the_components_company_id(): void
{
$company = Company::factory()->create();
$company = Company::factory()->create();
$component = Component::factory()->create(['company_id' => $company->id]);
$asset = Asset::factory()->create();
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.components.checkout', $component->id), [
'assigned_to' => $asset->id,
'assigned_to' => $asset->id,
'assigned_qty' => 1,
])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Component::class,
'item_id' => $component->id,
'item_type' => Component::class,
'item_id' => $component->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
public function test_component_checkin_log_stores_the_components_company_id(): void
{
$company = Company::factory()->create();
$company = Company::factory()->create();
$component = Component::factory()->create(['company_id' => $company->id]);
$asset = Asset::factory()->create();
$admin = User::factory()->superuser()->create();
$asset = Asset::factory()->create();
$admin = User::factory()->superuser()->create();
// Check out first
$this->actingAsForApi($admin)
->postJson(route('api.components.checkout', $component->id), [
'assigned_to' => $asset->id,
'assigned_to' => $asset->id,
'assigned_qty' => 1,
])
->assertStatusMessageIs('success');
@@ -258,10 +259,10 @@ class ActionlogCompanyIdTest extends TestCase
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Component::class,
'item_id' => $component->id,
'item_type' => Component::class,
'item_id' => $component->id,
'action_type' => 'checkin from',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -273,9 +274,9 @@ class ActionlogCompanyIdTest extends TestCase
{
$company = Company::factory()->create();
$license = License::factory()->create(['company_id' => $company->id]);
$seat = $license->freeSeats()->first();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$seat = $license->freeSeats()->first();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->patchJson(route('api.licenses.seats.update', [$license->id, $seat->id]), [
@@ -285,10 +286,10 @@ class ActionlogCompanyIdTest extends TestCase
// The log is stored against the License (item_type), not the LicenseSeat
$this->assertDatabaseHas('action_logs', [
'item_type' => License::class,
'item_id' => $license->id,
'item_type' => License::class,
'item_id' => $license->id,
'action_type' => 'checkout',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -296,9 +297,9 @@ class ActionlogCompanyIdTest extends TestCase
{
$company = Company::factory()->create();
$license = License::factory()->create(['company_id' => $company->id]);
$seat = $license->freeSeats()->first();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$seat = $license->freeSeats()->first();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
// Check out first
$seat->assigned_to = $user->id;
@@ -311,10 +312,10 @@ class ActionlogCompanyIdTest extends TestCase
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => License::class,
'item_id' => $license->id,
'item_type' => License::class,
'item_id' => $license->id,
'action_type' => 'checkin from',
'company_id' => $company->id,
'company_id' => $company->id,
]);
}
@@ -332,33 +333,32 @@ class ActionlogCompanyIdTest extends TestCase
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'audit',
'company_id' => null,
'company_id' => null,
]);
}
public function test_asset_checkout_log_company_id_is_null_when_asset_has_no_company(): void
{
$asset = Asset::factory()->create(['company_id' => null]);
$user = User::factory()->create();
$user = User::factory()->create();
$admin = User::factory()->superuser()->create();
$this->actingAsForApi($admin)
->postJson(route('api.asset.checkout', $asset), [
'checkout_to_type' => 'user',
'assigned_user' => $user->id,
'status_id' => $asset->status_id,
'assigned_user' => $user->id,
'status_id' => $asset->status_id,
])
->assertStatusMessageIs('success');
$this->assertDatabaseHas('action_logs', [
'item_type' => Asset::class,
'item_id' => $asset->id,
'item_type' => Asset::class,
'item_id' => $asset->id,
'action_type' => 'checkout',
'company_id' => null,
'company_id' => null,
]);
}
}