defmodule GitGud.SecretsTest do
use GitGud.DataCase, async: false
alias GitGud.Organizations
alias GitGud.Repositories
alias GitGud.Secrets
import GitGud.AccountsFixtures
import GitGud.ForgeFixtures
@moduletag :git_required
setup do
if System.find_executable("git") == nil do
{:skip, "git not on PATH"}
else
:ok
end
end
describe "encrypt/decrypt" do
test "round-trips arbitrary bytes" do
assert {:ok, enc} = Secrets.encrypt("hello world")
assert {:ok, "hello world"} = Secrets.decrypt(enc)
end
test "tampered ciphertext fails decrypt" do
{:ok, enc} = Secrets.encrypt("hello")
bad = %{enc | ciphertext: <<0::size(byte_size(enc.ciphertext) * 8)>>}
assert {:error, :decrypt_failed} = Secrets.decrypt(bad)
end
end
describe "repo secret CRUD" do
test "put / list / delete a repo secret" do
{_user, repo} = repository_fixture()
assert {:ok, _} = Secrets.put_repo_secret(repo, "API_KEY", "sk-test")
assert "API_KEY" in Secrets.list_repo_secret_names(repo)
# Listing exposes names only, never values.
:ok = Secrets.delete_repo_secret(repo, "API_KEY")
assert Secrets.list_repo_secret_names(repo) == []
end
test "rejects names that aren't valid env-var identifiers" do
{_user, repo} = repository_fixture()
assert {:error, cs} = Secrets.put_repo_secret(repo, "1invalid", "x")
assert "must be a valid env var name" <> _ = hd(errors_on(cs).name)
end
test "upsert replaces an existing secret without surfacing the old value" do
{_user, repo} = repository_fixture()
{:ok, _} = Secrets.put_repo_secret(repo, "KEY", "first")
{:ok, _} = Secrets.put_repo_secret(repo, "KEY", "second")
# No way to read it back via the public API, but resolve_for/1
# exposes plaintext to runner-bound code:
{secrets, _vars} = Secrets.resolve_for(repo)
assert secrets["KEY"] == "second"
end
end
describe "resolution precedence" do
test "repo secret overrides org secret with the same name" do
user = user_fixture()
{:ok, org} = Organizations.create_organization(user, %{"handle" => "secz"})
{:ok, repo} = Repositories.create_repository_for_org(org, user, %{"name" => "app"})
{:ok, _} = Secrets.put_org_secret(org, "TOKEN", "org-value")
{:ok, _} = Secrets.put_repo_secret(repo, "TOKEN", "repo-value")
{secrets, _} = Secrets.resolve_for(repo)
assert secrets["TOKEN"] == "repo-value"
end
test "org-only secrets surface on the repo when not shadowed" do
user = user_fixture()
{:ok, org} = Organizations.create_organization(user, %{"handle" => "secz2"})
{:ok, repo} = Repositories.create_repository_for_org(org, user, %{"name" => "app2"})
{:ok, _} = Secrets.put_org_secret(org, "ONLY_ORG", "shared")
{secrets, _} = Secrets.resolve_for(repo)
assert secrets["ONLY_ORG"] == "shared"
end
test "variables follow the same precedence" do
user = user_fixture()
{:ok, org} = Organizations.create_organization(user, %{"handle" => "varz"})
{:ok, repo} = Repositories.create_repository_for_org(org, user, %{"name" => "app"})
{:ok, _} = Secrets.put_org_variable(org, "REGION", "us-east")
{:ok, _} = Secrets.put_repo_variable(repo, "REGION", "eu-west")
{_, vars} = Secrets.resolve_for(repo)
assert vars["REGION"] == "eu-west"
end
end
end
3.4 KiB · text
5af55d9