mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
Mod Version Dependency Updates
- Handles circular mod version dependencies - Optimizes mod show query to only pull the versions relationship - Adds a mod dependency factory - Refactored tests to use mod dependency factory - Adds mod dependency generation into the default seeder - Adds unique index on mod dependencies table - Adds mod dependencies on the mod show view
This commit is contained in:
parent
20e1ab2dae
commit
c6f252ace7
10
app/Exceptions/CircularDependencyException.php
Normal file
10
app/Exceptions/CircularDependencyException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CircularDependencyException extends Exception
|
||||
{
|
||||
protected $message = 'Circular dependency detected.';
|
||||
}
|
@ -31,20 +31,23 @@ class ModController extends Controller
|
||||
->with([
|
||||
'versions',
|
||||
'versions.sptVersion',
|
||||
'latestVersion',
|
||||
'latestVersion.sptVersion',
|
||||
'versions.dependencies',
|
||||
'versions.dependencies.resolvedVersion',
|
||||
'versions.dependencies.resolvedVersion.mod',
|
||||
'users:id,name',
|
||||
'license:id,name,link',
|
||||
])
|
||||
->find($modId);
|
||||
->findOrFail($modId);
|
||||
|
||||
if (! $mod || $mod->slug !== $slug) {
|
||||
if ($mod->slug !== $slug) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$this->authorize('view', $mod);
|
||||
|
||||
return view('mod.show', compact('mod'));
|
||||
$latestVersion = $mod->versions->sortByDesc('created_at')->first();
|
||||
|
||||
return view('mod.show', compact(['mod', 'latestVersion']));
|
||||
}
|
||||
|
||||
public function update(ModRequest $request, Mod $mod)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
@ -14,6 +15,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
*/
|
||||
class ModDependency extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The relationship between a mod dependency and mod version.
|
||||
*/
|
||||
|
@ -2,58 +2,98 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use Composer\Semver\Semver;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class ModVersionService
|
||||
{
|
||||
// TODO: This works, but it needs to be refactored. It's too big and does too much.
|
||||
protected array $visited = [];
|
||||
|
||||
protected array $stack = [];
|
||||
|
||||
/**
|
||||
* Resolve dependencies for the given mod version.
|
||||
*
|
||||
* @throws CircularDependencyException
|
||||
*/
|
||||
public function resolveDependencies(ModVersion $modVersion): array
|
||||
{
|
||||
$resolvedVersions = [];
|
||||
$this->visited = [];
|
||||
$this->stack = [];
|
||||
|
||||
try {
|
||||
// Eager load dependencies with related mod versions
|
||||
$dependencies = $modVersion->dependencies()->with(['dependencyMod.versions'])->get();
|
||||
|
||||
foreach ($dependencies as $dependency) {
|
||||
$dependencyMod = $dependency->dependencyMod;
|
||||
|
||||
// Ensure dependencyMod exists and has versions
|
||||
if (! $dependencyMod || $dependencyMod->versions->isEmpty()) {
|
||||
if ($dependency->resolved_version_id !== null) {
|
||||
$dependency->updateQuietly(['resolved_version_id' => null]);
|
||||
}
|
||||
$resolvedVersions[$dependency->id] = null;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get available versions in the form ['version' => 'id']
|
||||
$availableVersions = $dependencyMod->versions->pluck('id', 'version')->toArray();
|
||||
|
||||
// Find the latest version that satisfies the constraint
|
||||
$satisfyingVersions = Semver::satisfiedBy(array_keys($availableVersions), $dependency->version_constraint);
|
||||
|
||||
// Get the first element's id from satisfyingVersions
|
||||
$latestVersionId = $satisfyingVersions ? $availableVersions[reset($satisfyingVersions)] : null;
|
||||
|
||||
// Update the resolved version ID in the ModDependency record
|
||||
if ($dependency->resolved_version_id !== $latestVersionId) {
|
||||
$dependency->updateQuietly(['resolved_version_id' => $latestVersionId]);
|
||||
}
|
||||
|
||||
// Add the resolved ModVersion to the array (or null if not found)
|
||||
$resolvedVersions[$dependency->id] = $latestVersionId ? ModVersion::find($latestVersionId) : null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error resolving dependencies for ModVersion: '.$modVersion->id, [
|
||||
'exception' => $e->getMessage(),
|
||||
'mod_version_id' => $modVersion->id,
|
||||
]);
|
||||
}
|
||||
$this->processDependencies($modVersion, $resolvedVersions);
|
||||
|
||||
return $resolvedVersions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a depth-first search to resolve dependencies for the given mod version.
|
||||
*
|
||||
* @throws CircularDependencyException
|
||||
*/
|
||||
protected function processDependencies(ModVersion $modVersion, array &$resolvedVersions): void
|
||||
{
|
||||
if (in_array($modVersion->id, $this->stack)) {
|
||||
throw new CircularDependencyException("Circular dependency detected in ModVersion ID: {$modVersion->id}");
|
||||
}
|
||||
|
||||
if (in_array($modVersion->id, $this->visited)) {
|
||||
return; // Skip already processed versions
|
||||
}
|
||||
|
||||
$this->visited[] = $modVersion->id;
|
||||
$this->stack[] = $modVersion->id;
|
||||
|
||||
/** @var Collection|ModDependency[] $dependencies */
|
||||
$dependencies = $this->getDependencies($modVersion);
|
||||
|
||||
foreach ($dependencies as $dependency) {
|
||||
$resolvedVersionId = $this->resolveVersionIdForDependency($dependency);
|
||||
|
||||
if ($dependency->resolved_version_id !== $resolvedVersionId) {
|
||||
$dependency->updateQuietly(['resolved_version_id' => $resolvedVersionId]);
|
||||
}
|
||||
|
||||
$resolvedVersions[$dependency->id] = $resolvedVersionId ? ModVersion::find($resolvedVersionId) : null;
|
||||
|
||||
if ($resolvedVersionId) {
|
||||
$nextModVersion = ModVersion::find($resolvedVersionId);
|
||||
if ($nextModVersion) {
|
||||
$this->processDependencies($nextModVersion, $resolvedVersions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($this->stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies for the given mod version.
|
||||
*/
|
||||
protected function getDependencies(ModVersion $modVersion): Collection
|
||||
{
|
||||
return $modVersion->dependencies()->with(['dependencyMod.versions'])->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the latest version ID that satisfies the version constraint on given dependency.
|
||||
*/
|
||||
protected function resolveVersionIdForDependency(ModDependency $dependency): ?int
|
||||
{
|
||||
$mod = $dependency->dependencyMod;
|
||||
|
||||
if (! $mod || $mod->versions->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$availableVersions = $mod->versions->pluck('id', 'version')->toArray();
|
||||
$satisfyingVersions = Semver::satisfiedBy(array_keys($availableVersions), $dependency->version_constraint);
|
||||
|
||||
// Versions are sorted in descending order by default. Take the first key (the latest version) using `reset()`.
|
||||
return $satisfyingVersions ? $availableVersions[reset($satisfyingVersions)] : null;
|
||||
}
|
||||
}
|
||||
|
25
database/factories/ModDependencyFactory.php
Normal file
25
database/factories/ModDependencyFactory.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ModDependencyFactory extends Factory
|
||||
{
|
||||
protected $model = ModDependency::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'mod_version_id' => ModVersion::factory(),
|
||||
'dependency_mod_id' => Mod::factory(),
|
||||
'version_constraint' => '^'.$this->faker->numerify('#.#.#'),
|
||||
'created_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
'updated_at' => Carbon::now()->subDays(rand(0, 365))->subHours(rand(0, 23)),
|
||||
];
|
||||
}
|
||||
}
|
@ -25,6 +25,8 @@ return new class extends Migration
|
||||
->nullOnDelete()
|
||||
->cascadeOnUpdate();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['mod_version_id', 'dependency_mod_id', 'version_constraint'], 'mod_dependencies_unique');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\License;
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
use App\Models\SptVersion;
|
||||
use App\Models\User;
|
||||
@ -46,6 +48,31 @@ class DatabaseSeeder extends Seeder
|
||||
}
|
||||
|
||||
// Add 1000 mod versions, assigning them to the mods we just created.
|
||||
ModVersion::factory(1000)->recycle([$mods, $spt_versions])->create();
|
||||
$modVersions = ModVersion::factory(1000)->recycle([$mods, $spt_versions])->create();
|
||||
|
||||
// Add ModDependencies to a subset of ModVersions.
|
||||
foreach ($modVersions as $modVersion) {
|
||||
$hasDependencies = rand(0, 100) < 30; // 30% chance to have dependencies
|
||||
if ($hasDependencies) {
|
||||
$numDependencies = rand(1, 3); // 1 to 3 dependencies
|
||||
$dependencyMods = $mods->random($numDependencies);
|
||||
foreach ($dependencyMods as $dependencyMod) {
|
||||
try {
|
||||
ModDependency::factory()->recycle([$modVersion, $dependencyMod])->create([
|
||||
'version_constraint' => $this->generateVersionConstraint(),
|
||||
]);
|
||||
} catch (CircularDependencyException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateVersionConstraint(): string
|
||||
{
|
||||
$versionConstraints = ['*', '^1.0.0', '>=2.0.0', '~1.1.0', '>=1.2.0 <2.0.0'];
|
||||
|
||||
return $versionConstraints[array_rand($versionConstraints)];
|
||||
}
|
||||
}
|
||||
|
@ -25,15 +25,15 @@
|
||||
<h2 class="pb-1 sm:p-0 text-3xl font-bold text-gray-900 dark:text-white">
|
||||
{{ $mod->name }}
|
||||
<span class="font-light text-nowrap text-gray-700 dark:text-gray-400">
|
||||
{{ $mod->latestVersion->version }}
|
||||
{{ $latestVersion->version }}
|
||||
</span>
|
||||
</h2>
|
||||
<span class="badge-version {{ $mod->latestVersion->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->sptVersion->version }}
|
||||
<span class="badge-version {{ $latestVersion->sptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $latestVersion->sptVersion->version }}
|
||||
</span>
|
||||
</div>
|
||||
<p>{{ __('Created by') }} {{ $mod->users->pluck('name')->implode(', ') }}</p>
|
||||
<p>{{ $mod->latestVersion->sptVersion->version }} {{ __('Compatible') }}</p>
|
||||
<p>{{ $latestVersion->sptVersion->version }} {{ __('Compatible') }}</p>
|
||||
<p>{{ Number::format($mod->total_downloads) }} {{ __('Downloads') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,12 +94,22 @@
|
||||
</span>
|
||||
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-gray-400">
|
||||
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
|
||||
<span>{{ __('Created') }} {{ $version->created_at->format("M d, h:m a") }}</span>
|
||||
<span>{{ __('Updated') }} {{ $version->updated_at->format("M d, h:m a") }}</span>
|
||||
</div>
|
||||
@if ($version->dependencies->count())
|
||||
<div class="text-gray-600 dark:text-gray-400">
|
||||
{{ __('Dependencies:') }}
|
||||
@foreach ($version->dependencies as $dependency)
|
||||
<a href="{{ route('mod.show', [$dependency->resolvedVersion->mod->id, $dependency->resolvedVersion->mod->slug]) }}">
|
||||
{{ $dependency->resolvedVersion->mod->name }} ({{ $dependency->resolvedVersion->version }})
|
||||
</a>@if (!$loop->last), @endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="pt-3 user-markdown">
|
||||
<div class="p-3 user-markdown">
|
||||
{{-- The description below is safe to write directly because it has been run though HTMLPurifier during the import process. --}}
|
||||
{!! Str::markdown($version->description) !!}
|
||||
</div>
|
||||
@ -118,15 +128,15 @@
|
||||
<div class="col-span-1 flex flex-col gap-6">
|
||||
|
||||
{{-- Main Download Button --}}
|
||||
<a href="{{ $mod->latestVersion->link }}" class="block">
|
||||
<button class="text-lg font-extrabold hover:bg-cyan-400 dark:hover:bg-cyan-600 shadow-md dark:shadow-gray-950 drop-shadow-2xl bg-cyan-500 dark:bg-cyan-700 rounded-xl w-full h-20">{{ __('Download Latest Version') }} ({{ $mod->latestVersion->version }})</button>
|
||||
<a href="{{ $latestVersion->link }}" class="block">
|
||||
<button class="text-lg font-extrabold hover:bg-cyan-400 dark:hover:bg-cyan-600 shadow-md dark:shadow-gray-950 drop-shadow-2xl bg-cyan-500 dark:bg-cyan-700 rounded-xl w-full h-20">{{ __('Download Latest Version') }} ({{ $latestVersion->version }})</button>
|
||||
</a>
|
||||
|
||||
{{-- Additional Mod Details --}}
|
||||
<div class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ __('Details') }}</h2>
|
||||
<ul role="list" class="divide-y divide-gray-200 dark:divide-gray-800 text-gray-900 dark:text-gray-100">
|
||||
@if($mod->license)
|
||||
@if ($mod->license)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('License') }}</h3>
|
||||
<p class="truncate">
|
||||
@ -136,7 +146,7 @@
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->source_code_link)
|
||||
@if ($mod->source_code_link)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Source Code') }}</h3>
|
||||
<p class="truncate">
|
||||
@ -146,17 +156,29 @@
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->latestVersion->virus_total_link)
|
||||
@if ($latestVersion->virus_total_link)
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Latest VirusTotal Result') }}</h3>
|
||||
<h3>{{ __('Latest Version VirusTotal Result') }}</h3>
|
||||
<p class="truncate">
|
||||
<a href="{{ $mod->latestVersion->virus_total_link }}" title="{{ $mod->latestVersion->virus_total_link }}" target="_blank">
|
||||
{{ $mod->latestVersion->virus_total_link }}
|
||||
<a href="{{ $latestVersion->virus_total_link }}" title="{{ $latestVersion->virus_total_link }}" target="_blank">
|
||||
{{ $latestVersion->virus_total_link }}
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->contains_ads)
|
||||
@if ($latestVersion->dependencies->count())
|
||||
<li class="px-4 py-4 sm:px-0">
|
||||
<h3>{{ __('Latest Version Dependencies') }}</h3>
|
||||
<p class="truncate">
|
||||
@foreach ($latestVersion->dependencies as $dependency)
|
||||
<a href="{{ route('mod.show', [$dependency->resolvedVersion->mod->id, $dependency->resolvedVersion->mod->slug]) }}">
|
||||
{{ $dependency->resolvedVersion->mod->name }} ({{ $dependency->resolvedVersion->version }})
|
||||
</a><br />
|
||||
@endforeach
|
||||
</p>
|
||||
</li>
|
||||
@endif
|
||||
@if ($mod->contains_ads)
|
||||
<li class="px-4 py-4 sm:px-0 flex flex-row gap-2 items-center">
|
||||
<svg class="grow-0 w-[16px] h-[16px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"/>
|
||||
@ -166,7 +188,7 @@
|
||||
</h3>
|
||||
</li>
|
||||
@endif
|
||||
@if($mod->contains_ai_content)
|
||||
@if ($mod->contains_ai_content)
|
||||
<li class="px-4 py-4 sm:px-0 flex flex-row gap-2 items-center">
|
||||
<svg class="grow-0 w-[16px] h-[16px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Zm3.844-8.791a.75.75 0 0 0-1.188-.918l-3.7 4.79-1.649-1.833a.75.75 0 1 0-1.114 1.004l2.25 2.5a.75.75 0 0 0 1.15-.043l4.25-5.5Z" clip-rule="evenodd"/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Exceptions\CircularDependencyException;
|
||||
use App\Models\Mod;
|
||||
use App\Models\ModDependency;
|
||||
use App\Models\ModVersion;
|
||||
@ -19,13 +20,12 @@ it('resolves mod version dependency when mod version is created', function () {
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
$modDependency->refresh();
|
||||
|
||||
expect($modDependency->resolvedVersion->version)->toBe('1.1.1');
|
||||
});
|
||||
|
||||
@ -41,9 +41,7 @@ it('resolves mod version dependency when mod version is updated', function () {
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
@ -69,9 +67,7 @@ it('resolves mod version dependency when mod version is deleted', function () {
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
@ -98,9 +94,7 @@ it('resolves mod version dependency after semantic version constraint is updated
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
@ -126,9 +120,7 @@ it('resolves mod version dependency with exact semantic version constraint', fun
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '1.1.0',
|
||||
]);
|
||||
|
||||
@ -150,9 +142,7 @@ it('resolves mod version dependency with complex semantic version constraint', f
|
||||
|
||||
// Create versions for Mod A that depends on Mod B
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '>=1.0.0 <2.0.0',
|
||||
]);
|
||||
|
||||
@ -171,9 +161,7 @@ it('resolves null when no mod versions are available', function () {
|
||||
|
||||
// Create version for Mod A that has no resolvable dependency
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
@ -192,9 +180,7 @@ it('resolves null when no mod versions match against semantic version constraint
|
||||
|
||||
// Create version for Mod A that has no resolvable dependency
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
$modDependency = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependency = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '~1.2.0',
|
||||
]);
|
||||
|
||||
@ -220,14 +206,10 @@ it('resolves multiple dependencies', function () {
|
||||
// Creating a version for Mod A that depends on Mod B and Mod C
|
||||
$modAv1 = ModVersion::factory()->create(['mod_id' => $modA->id, 'version' => '1.0.0']);
|
||||
|
||||
$modDependencyB = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modB->id,
|
||||
$modDependencyB = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
$modDependencyC = ModDependency::create([
|
||||
'mod_version_id' => $modAv1->id,
|
||||
'dependency_mod_id' => $modC->id,
|
||||
$modDependencyC = ModDependency::factory()->recycle([$modAv1, $modC])->create([
|
||||
'version_constraint' => '^1.0.0',
|
||||
]);
|
||||
|
||||
@ -237,3 +219,20 @@ it('resolves multiple dependencies', function () {
|
||||
$modDependencyC->refresh();
|
||||
expect($modDependencyC->resolvedVersion->version)->toBe('1.1.1');
|
||||
});
|
||||
|
||||
it('throws exception when there is a circular version dependency', function () {
|
||||
$modA = Mod::factory()->create(['name' => 'Mod A']);
|
||||
$modAv1 = ModVersion::factory()->recycle($modA)->create(['version' => '1.0.0']);
|
||||
|
||||
$modB = Mod::factory()->create(['name' => 'Mod B']);
|
||||
$modBv1 = ModVersion::factory()->recycle($modB)->create(['version' => '1.0.0']);
|
||||
|
||||
$modDependencyAtoB = ModDependency::factory()->recycle([$modAv1, $modB])->create([
|
||||
'version_constraint' => '1.0.0',
|
||||
]);
|
||||
|
||||
// Create circular dependencies
|
||||
$modDependencyBtoA = ModDependency::factory()->recycle([$modBv1, $modA])->create([
|
||||
'version_constraint' => '1.0.0',
|
||||
]);
|
||||
})->throws(CircularDependencyException::class);
|
||||
|
Loading…
x
Reference in New Issue
Block a user