fix(resources): preserve records sharing IDs across model types (#11300)

This commit is contained in:
Andras Bacsai 2026-08-15 18:43:20 +02:00 committed by GitHub
parent bb9f5e5768
commit 1440d35750
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 109 additions and 9 deletions

View file

@ -70,7 +70,7 @@ public function getResourcesPreview(): array
return [
'applications' => $applications->unique('id'),
'databases' => $databases->unique('id'),
'databases' => $databases->unique(fn ($database) => $database::class.':'.$database->id),
'services' => $services->unique('id'),
];
}

View file

@ -166,13 +166,13 @@ public function isEmpty()
public function databases(array $with = []): Collection
{
return $this->postgresqls()->with($with)->get()
->merge($this->redis()->with($with)->get())
->merge($this->mongodbs()->with($with)->get())
->merge($this->mysqls()->with($with)->get())
->merge($this->mariadbs()->with($with)->get())
->merge($this->keydbs()->with($with)->get())
->merge($this->dragonflies()->with($with)->get())
->merge($this->clickhouses()->with($with)->get());
->concat($this->redis()->with($with)->get())
->concat($this->mongodbs()->with($with)->get())
->concat($this->mysqls()->with($with)->get())
->concat($this->mariadbs()->with($with)->get())
->concat($this->keydbs()->with($with)->get())
->concat($this->dragonflies()->with($with)->get())
->concat($this->clickhouses()->with($with)->get());
}
public function navigateTo()

View file

@ -107,7 +107,7 @@ protected static function booted()
// Delete non-instance-wide sources owned by this team
$teamSources = GithubApp::where('team_id', $team->id)->get()
->merge(GitlabApp::where('team_id', $team->id)->get());
->concat(GitlabApp::where('team_id', $team->id)->get());
foreach ($teamSources as $source) {
$source->delete();
}

View file

@ -0,0 +1,40 @@
<?php
use App\Models\Project;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneRedis;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('databases includes different database types with the same primary key', function () {
$team = Team::factory()->create();
$server = Server::factory()->create(['team_id' => $team->id]);
$destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
$project = Project::factory()->create(['team_id' => $team->id]);
$environment = $project->environments()->firstOrFail();
StandaloneRedis::forceCreate([
'id' => 42,
'name' => 'redis',
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
StandaloneMariadb::forceCreate([
'id' => 42,
'name' => 'mariadb',
'mariadb_root_password' => 'password',
'mariadb_password' => 'password',
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
expect($project->redis()->count())->toBe(1)
->and($project->mariadbs()->count())->toBe(1)
->and($project->databases())->toHaveCount(2);
});

View file

@ -1,6 +1,8 @@
<?php
use App\Livewire\Team\DangerZone;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
@ -52,3 +54,27 @@
expect(session('currentTeam'))->toBeNull();
});
test('deleting a team deletes github and gitlab sources with the same primary key', function () {
$githubApp = GithubApp::forceCreate([
'id' => 42,
'name' => 'GitHub source',
'team_id' => $this->teamToDelete->id,
'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com',
'is_public' => false,
]);
$gitlabApp = GitlabApp::forceCreate([
'id' => 42,
'name' => 'GitLab source',
'team_id' => $this->teamToDelete->id,
'api_url' => 'https://gitlab.com/api/v4',
'html_url' => 'https://gitlab.com',
'is_public' => false,
]);
$this->teamToDelete->delete();
expect(GithubApp::find($githubApp->id))->toBeNull()
->and(GitlabApp::find($gitlabApp->id))->toBeNull();
});

View file

@ -2,6 +2,8 @@
use App\Actions\User\DeleteUserResources;
use App\Models\Server;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneRedis;
use App\Models\Team;
use App\Models\User;
@ -16,6 +18,38 @@
Mockery::close();
});
it('keeps different database types with the same primary key in the preview', function () {
$teamPivot = (object) ['role' => 'owner'];
$team = Mockery::mock(Team::class);
$team->shouldReceive('getAttribute')->with('pivot')->andReturn($teamPivot);
$team->shouldReceive('getAttribute')->with('members')->andReturn(collect([$this->user]));
$team->shouldReceive('setAttribute')->andReturnSelf();
$team->pivot = $teamPivot;
$team->members = collect([$this->user]);
$redis = new StandaloneRedis;
$redis->id = 42;
$mariadb = new StandaloneMariadb;
$mariadb->id = 42;
$server = Mockery::mock(Server::class);
$server->shouldReceive('applications')->andReturn(collect());
$server->shouldReceive('databases')->andReturn(collect([$redis, $mariadb]));
$server->shouldReceive('services->get')->andReturn(collect());
$teamsRelation = Mockery::mock();
$teamsRelation->shouldReceive('get')->andReturn(collect([$team]));
$this->user->shouldReceive('teams')->andReturn($teamsRelation);
$serversRelation = Mockery::mock();
$serversRelation->shouldReceive('get')->andReturn(collect([$server]));
$team->shouldReceive('servers')->andReturn($serversRelation);
$preview = (new DeleteUserResources($this->user, true))->getResourcesPreview();
expect($preview['databases'])->toHaveCount(2);
});
it('only collects resources from teams where user is the sole member', function () {
// Mock owned team where user is the ONLY member (will be deleted)
$ownedTeamPivot = (object) ['role' => 'owner'];