defmodule GitGud.Packages.PrunerTest do
@moduledoc """
Deleting bytes: what goes, what is spared, and what happens when the
thing holding the bytes says no.
"""
use GitGud.DataCase, async: false
alias GitGud.Packages
alias GitGud.Packages.PackageFile
alias GitGud.Packages.PackageVersion
alias GitGud.Packages.Pruner
alias GitGud.Packages.Retention
alias GitGud.Settings
alias GitGud.Storage
import GitGud.ForgeFixtures
setup do
{user, repo} = repository_fixture()
%{user: user, repo: repo}
end
describe "prune/2 while enforcement is off" do
test "reports what it would do and touches nothing", %{repo: repo} do
package = package_with_versions(repo, 30)
assert {:ok, result} = Pruner.prune(package)
assert result.status == :reported
assert result.pruned == 6
assert result.bytes > 0
assert version_count(package) == 30
end
test "force overrides it, which is what the admin button does", %{repo: repo} do
package = package_with_versions(repo, 26)
assert {:ok, %{status: :pruned, pruned: 2}} = Pruner.prune(package, force: true)
assert version_count(package) == 24
end
end
describe "prune/2 with enforcement on" do
setup do
{:ok, _} = Settings.put("packages.retention_enforced", true)
:ok
end
test "deletes the oldest versions and their bytes", %{repo: repo} do
package = package_with_versions(repo, 26)
doomed = version(package, "1.0.0")
[file] = doomed.files
assert {:ok, _} = Storage.get(file.storage_bucket, file.storage_key)
assert {:ok, %{status: :pruned, pruned: 2, failed: []}} = Pruner.prune(package)
assert version_count(package) == 24
refute Repo.get(PackageVersion, doomed.id)
refute Repo.get(PackageFile, file.id)
# The bytes are gone too, not just the row pointing at them.
assert {:error, _} = Storage.get(file.storage_bucket, file.storage_key)
end
test "keeps bytes another version still points at", %{repo: repo} do
# Two versions publishing byte-identical files share one
# content-addressed object; pruning the older must not pull the
# object out from under the newer.
name = "shared#{System.unique_integer([:positive])}"
body = "identical bytes\n"
for i <- 0..25 do
{:ok, _, _, _} =
Packages.publish_file(repo, "pypi", name, "1.0.#{i}", %{
filename: "#{name}-1.0.#{i}.tar.gz",
body: body,
content_type: "application/gzip",
pushed_at: DateTime.add(~U[2026-01-01 00:00:00Z], i, :second)
})
end
package = Packages.get_package(repo, "pypi", name)
[survivor_file] = version(package, "1.0.25").files
assert {:ok, %{pruned: 2}} = Pruner.prune(package)
assert {:ok, ^body} = Storage.get(survivor_file.storage_bucket, survivor_file.storage_key)
end
test "a pinned version survives and the next-oldest goes instead", %{repo: repo, user: user} do
package = package_with_versions(repo, 26)
{:ok, _} = Retention.pin(version(package, "1.0.0"), user)
assert {:ok, %{pruned: 1}} = Pruner.prune(package)
assert version(package, "1.0.0")
refute Repo.get_by(PackageVersion, package_id: package.id, version: "1.0.1")
end
test "latest_version is recomputed, never left dangling", %{repo: repo} do
{:ok, _} = Settings.put("packages.retention_mode", "quota")
{:ok, _} = Settings.put("packages.retention_bytes", 100)
package = package_with_versions(repo, 5, bytes: 1_000)
assert {:ok, %{status: :pruned}} = Pruner.prune(package)
package = Repo.reload(package)
assert package.latest_version == "1.0.4"
assert version_count(package) == 1
end
test "prune_all walks every package", %{repo: repo} do
package_with_versions(repo, 26)
package_with_versions(repo, 27)
assert {:ok, %{status: :pruned, pruned: 5}} = Pruner.prune_all()
end
test "prune_all can be scoped to one repository", %{repo: repo, user: user} do
{_user, other} = repository_fixture(%{owner: user})
package_with_versions(repo, 26)
package_with_versions(other, 30)
assert {:ok, %{pruned: 2}} = Pruner.prune_all(repository: repo)
# The other repo is untouched.
assert {:ok, %{pruned: 6}} = Pruner.prune_all(repository: other)
end
end
# ── helpers ──────────────────────────────────────────────────────────
defp package_with_versions(repo, count, opts \\ []) do
bytes = Keyword.get(opts, :bytes, 32)
name = "widget#{System.unique_integer([:positive])}"
for i <- 0..(count - 1) do
{:ok, _, _, _} =
Packages.publish_file(repo, "pypi", name, "1.0.#{i}", %{
filename: "#{name}-1.0.#{i}.tar.gz",
body: :crypto.strong_rand_bytes(bytes),
content_type: "application/gzip",
pushed_at: DateTime.add(~U[2026-01-01 00:00:00Z], i, :second)
})
end
Packages.get_package(repo, "pypi", name)
end
defp version(package, version) do
PackageVersion
|> Repo.get_by!(package_id: package.id, version: version)
|> Repo.preload(:files)
end
defp version_count(package) do
Repo.aggregate(
from(v in PackageVersion, where: v.package_id == ^package.id),
:count
)
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
5.4 KiB · text
History
aa9e861