6.1 KiB · text 5af55d9
defmodule GitGud.RepositoriesLanguageStatsTest do
@moduledoc """
Walks `compute_and_store_language_stats/2` against synthesized PG
caches — no git CLI needed.
"""
use GitGud.DataCase, async: false
alias GitGud.Repo
alias GitGud.Repositories
alias GitGud.Repositories.GitCommit
alias GitGud.Repositories.GitCommitLanguageStats
alias GitGud.Repositories.GitTree
import GitGud.ForgeFixtures
defp insert_tree(rid, sha_hex, items) do
encoded = %{
"items" =>
Enum.map(items, fn item ->
%{
"name" => item.name,
"mode" => "100644",
"type" => item.type,
"sha" => item.sha,
"size" => Map.get(item, :size)
}
end)
}
Repo.insert_all(GitTree, [
%{repository_id: rid, sha: hex(sha_hex), entries: encoded}
])
end
defp insert_commit(rid, sha_hex, tree_sha_hex) do
Repo.insert_all(GitCommit, [
%{
repository_id: rid,
sha: hex(sha_hex),
tree_sha: hex(tree_sha_hex),
parent_shas: [],
author_name: "t",
author_email: "t@e",
authored_at: DateTime.utc_now(:second),
committer_name: "t",
committer_email: "t@e",
committed_at: DateTime.utc_now(:second),
message: "init"
}
])
end
defp hex(s), do: Base.decode16!(s, case: :mixed)
setup do
{_user, repo} = repository_fixture()
{:ok, repo: repo}
end
describe "compute_and_store_language_stats/2" do
test "buckets blob bytes by filename, sorted desc", %{repo: repo} do
# Tree:
# a.ex (Elixir, 100 bytes)
# b.ex (Elixir, 200 bytes)
# c.go (Go, 150 bytes)
# img.png (no extension match → skipped)
# LICENSE (no extension match → skipped)
tree_sha = String.duplicate("a", 40)
commit_sha = String.duplicate("b", 40)
shas = %{
b1: String.duplicate("1", 40),
b2: String.duplicate("2", 40),
b3: String.duplicate("3", 40),
bin: String.duplicate("4", 40),
license: String.duplicate("5", 40)
}
insert_tree(repo.id, tree_sha, [
%{name: "a.ex", type: "blob", sha: shas.b1, size: 100},
%{name: "b.ex", type: "blob", sha: shas.b2, size: 200},
%{name: "c.go", type: "blob", sha: shas.b3, size: 150},
%{name: "img.png", type: "blob", sha: shas.bin, size: 9999},
%{name: "LICENSE", type: "blob", sha: shas.license, size: 42}
])
insert_commit(repo.id, commit_sha, tree_sha)
assert {:ok, stats} =
Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
assert stats.total_bytes == 450
assert stats.file_count == 3
assert [
%{language: "Elixir", bytes: 300, files: 2},
%{language: "Go", bytes: 150, files: 1}
] = stats.languages
end
test "recurses into subtrees", %{repo: repo} do
root_sha = String.duplicate("a", 40)
sub_sha = String.duplicate("b", 40)
commit_sha = String.duplicate("c", 40)
blob1 = String.duplicate("1", 40)
blob2 = String.duplicate("2", 40)
insert_tree(repo.id, root_sha, [
%{name: "lib", type: "tree", sha: sub_sha, size: nil},
%{name: "top.ex", type: "blob", sha: blob1, size: 50}
])
insert_tree(repo.id, sub_sha, [
%{name: "nested.ex", type: "blob", sha: blob2, size: 70}
])
insert_commit(repo.id, commit_sha, root_sha)
assert {:ok, stats} =
Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
assert stats.total_bytes == 120
assert stats.file_count == 2
end
test "writes to git_commit_language_stats", %{repo: repo} do
tree_sha = String.duplicate("e", 40)
commit_sha = String.duplicate("f", 40)
blob = String.duplicate("1", 40)
insert_tree(repo.id, tree_sha, [
%{name: "x.ex", type: "blob", sha: blob, size: 10}
])
insert_commit(repo.id, commit_sha, tree_sha)
{:ok, _} = Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
row = Repo.get_by(GitCommitLanguageStats, repository_id: repo.id, sha: hex(commit_sha))
assert row
assert row.total_bytes == 10
assert row.file_count == 1
assert %{"items" => [%{"language" => "Elixir"}]} = row.languages
end
test "second compute replaces stale numbers", %{repo: repo} do
tree_sha = String.duplicate("a", 40)
commit_sha = String.duplicate("b", 40)
blob = String.duplicate("c", 40)
blob2 = String.duplicate("e", 40)
# First version of the tree: one .ex file.
insert_tree(repo.id, tree_sha, [%{name: "x.ex", type: "blob", sha: blob, size: 10}])
insert_commit(repo.id, commit_sha, tree_sha)
{:ok, _} = Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
# Now imagine the cached tree was wrong — rewrite it as a .go file
# and recompute. The conflict-replace behavior should produce a
# fresh row matching the updated tree.
Repo.delete_all(
from t in GitGud.Repositories.GitTree, where: t.sha == ^hex(tree_sha)
)
insert_tree(repo.id, tree_sha, [%{name: "x.go", type: "blob", sha: blob2, size: 10}])
{:ok, stats} = Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
assert [%{language: "Go"}] = stats.languages
end
end
describe "get_language_stats/2" do
test "returns :not_computed when no row", %{repo: repo} do
assert :not_computed = Repositories.get_language_stats(repo, String.duplicate("\0", 20))
end
test "returns {:ok, stats} after compute", %{repo: repo} do
tree_sha = String.duplicate("a", 40)
commit_sha = String.duplicate("b", 40)
blob = String.duplicate("c", 40)
insert_tree(repo.id, tree_sha, [%{name: "x.ex", type: "blob", sha: blob, size: 10}])
insert_commit(repo.id, commit_sha, tree_sha)
{:ok, _} = Repositories.compute_and_store_language_stats(repo, hex(commit_sha))
assert {:ok, %{languages: [%{language: "Elixir"}]}} =
Repositories.get_language_stats(repo, hex(commit_sha))
end
end
end