Files
ubicloud/spec/routes/web/project/github_spec.rb
Jeremy Evans 01a4956abd Avoid **hash.merge(other_hash) constructions
While this works, it's less efficient than alternatives.  If the method
accepts an options hash, the `**` can be dropped, avoiding a hash
allocation. If the method accepts keywords, `**hash, **other_hash` can
be used.

However, in the cases where we are using these constructions, one of
the hashes is a literal hash, so these constructions result in
2-3 hash allocations:

* 1 for the literal hash
* 1 for the Hash#merge
* 1 if the keyword splat is converted to a positional hash

using `**hash, kw: value` reduces this to a single hash allocation,
in addition to being simpler.

For the create_cache_entry change in the specs, use an anonymous keyword
splat parameter.  This is done for simplicity, it doesn't save
allocations in this case (as all callers are providing literal keywords).
2025-05-02 00:56:06 +09:00

231 lines
9.9 KiB
Ruby

# frozen_string_literal: true
require "stripe"
require_relative "../spec_helper"
RSpec.describe Clover, "github" do
let(:user) { create_account }
let(:project) { user.create_project_with_default_policy("project-1") }
let(:project_wo_permissions) { user.create_project_with_default_policy("project-2", default_policy: nil) }
let(:installation) { GithubInstallation.create_with_id(installation_id: 123, name: "test-user", type: "User", project_id: project.id) }
let(:repository) { GithubRepository.create_with_id(name: "test-repo", installation_id: installation.id) }
before do
login(user.email)
allow(Config).to receive(:github_app_name).and_return("runner-app")
end
it "disabled when GitHub app name not provided" do
allow(Config).to receive(:github_app_name).and_return(nil)
visit project.path
within "#desktop-menu" do
expect { click_link "GitHub Runners" }.to raise_error Capybara::ElementNotFound
end
expect(page.title).to eq("Ubicloud - #{project.name}")
visit "#{project.path}/github"
expect(page.status_code).to eq(501)
expect(page.body).to eq "GitHub Action Runner integration is not enabled. Set GITHUB_APP_NAME to enable it."
end
it "raises forbidden when does not have permissions" do
project_wo_permissions
visit "#{project_wo_permissions.path}/github"
expect(page.title).to eq("Ubicloud - Forbidden")
expect(page.status_code).to eq(403)
expect(page).to have_content "Forbidden"
end
describe "setting" do
it "can connect GitHub account" do
visit "#{project.path}/github"
click_link "Connect New Account"
expect(page.status_code).to eq(200)
expect(page.driver.request.session["login_redirect"]).to eq("/apps/runner-app/installations/new")
end
it "can not connect GitHub account if project has no valid payment method" do
expect(Project).to receive(:from_ubid).and_return(project).at_least(:once)
expect(Config).to receive(:stripe_secret_key).and_return("secret_key").at_least(:once)
visit "#{project.path}/github/installation/create"
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - GitHub Runner Settings")
expect(page).to have_flash_error("Project doesn't have valid billing information")
end
it "shows new billing info button instead of connect account if project has no valid payment method" do
expect(Project).to receive(:from_ubid).and_return(project).at_least(:once)
expect(Config).to receive(:stripe_secret_key).and_return("secret_key").at_least(:once)
# rubocop:disable RSpec/VerifiedDoubles
expect(Stripe::Checkout::Session).to receive(:create).and_return(double(Stripe::Checkout::Session, url: ""))
# rubocop:enable RSpec/VerifiedDoubles
visit "#{project.path}/github"
click_button "New Billing Information"
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - Project Billing")
end
it "can list installations" do
ins1 = GithubInstallation.create_with_id(installation_id: 111, name: "test-user", type: "User", project_id: project.id)
ins2 = GithubInstallation.create_with_id(installation_id: 222, name: "test-org", type: "Organization", project_id: project.id)
visit "#{project.path}/github/setting"
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - GitHub Runner Settings")
expect(page).to have_content "test-user"
expect(page).to have_link "Configure", href: /\/apps\/runner-app\/installations\/#{ins1.installation_id}/
expect(page).to have_content "test-org"
expect(page).to have_link "Configure", href: /\/apps\/runner-app\/installations\/#{ins2.installation_id}/
end
it "enables cache for installation" do
installation.update(cache_enabled: false)
visit "#{project.path}/github/setting"
_csrf = find("form[action='#{project.path}/github/installation/#{installation.ubid}'] input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/installation/#{installation.ubid}", {cache_enabled: true, _csrf:}
expect(page.status_code).to eq(302)
expect(installation.reload.cache_enabled).to be true
end
it "disables cache for installation" do
installation.update(cache_enabled: true)
visit "#{project.path}/github/setting"
_csrf = find("form[action='#{project.path}/github/installation/#{installation.ubid}'] input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/installation/#{installation.ubid}", {cache_enabled: false, _csrf:}
expect(page.status_code).to eq(302)
expect(installation.reload.cache_enabled).to be false
end
it "raises not found when installation doesn't exist" do
visit "#{project.path}/github/installation/invalid_id"
expect(page.status_code).to eq(404)
end
end
describe "runner" do
it "can list active runners" do
runner_deleted = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").update(label: "wait_vm_destroy")
runner_with_job = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").update(label: "wait").subject
runner_with_job.update(runner_id: 2, vm_id: Prog::Vm::Nexus.assemble("dummy-public key", project.id, name: "runner-vm").id, workflow_job: {
"id" => 123,
"name" => "test-job",
"run_id" => 456,
"workflow_name" => "test-workflow"
})
runner_not_created = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo")
runner_concurrency_limit = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").update(label: "wait_concurrency_limit")
runner_wo_strand = GithubRunner.create_with_id(installation_id: installation.id, label: "ubicloud", repository_name: "my-repo")
visit "#{project.path}/github/runner"
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - Active Runners")
expect(page).to have_content runner_deleted.ubid
expect(page).to have_content "deleted"
expect(page).to have_content "Runner doesn't have a job yet"
expect(page).to have_content runner_with_job.ubid
expect(page).to have_content "creating"
expect(page).to have_link runner_with_job.workflow_job["workflow_name"], href: runner_with_job.run_url
expect(page).to have_link runner_with_job.workflow_job["name"], href: runner_with_job.job_url
expect(page).to have_content runner_not_created.ubid
expect(page).to have_content "not_created"
expect(page).to have_content runner_concurrency_limit.ubid
expect(page).to have_content "reached_concurrency_limit"
expect(page).to have_content runner_wo_strand.ubid
expect(page).to have_content "not_created"
end
it "can terminate runner" do
runner = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").subject
visit "#{project.path}/github/runner"
expect(page.status_code).to eq(200)
expect(page).to have_content runner.ubid
btn = find "#runner-#{runner.id} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(page.status_code).to eq(204)
visit "#{project.path}/github/runner"
expect(page).to have_flash_notice("Runner '#{runner.ubid}' forcibly terminated")
end
it "raises not found when runner not exists" do
visit "#{project.path}/github/runner/grv4tp3wnb7j7jm5d40wv72j0t"
expect(page.title).to eq("Ubicloud - ResourceNotFound")
expect(page.status_code).to eq(404)
expect(page).to have_content "ResourceNotFound"
end
end
describe "cache" do
def create_cache_entry(**)
GithubCacheEntry.create(key: "k#{Random.rand}", version: "v1", scope: "main", repository_id: repository.id, created_by: "3c9a861c-ab14-8218-a175-875ebb652f7b", committed_at: Time.now, **)
end
it "can list caches" do
create_cache_entry(size: nil, created_at: Time.now, last_accessed_at: nil)
create_cache_entry(size: 800, created_at: Time.now - 10 * 60, last_accessed_at: Time.now - 5 * 60)
create_cache_entry(size: 20.6 * 1024, created_at: Time.now - 4 * 24 * 60 * 60, last_accessed_at: Time.now - 3 * 60 * 60)
visit "#{project.path}/github/cache"
expect(page.status_code).to eq(200)
expect(page).to have_content "3 cache entries"
expect(page).to have_content "21.4 KB used"
expect(page).to have_content "created just now"
expect(page).to have_content "Never used"
expect(page).to have_content "800 B"
expect(page).to have_content "created 10 minutes ago"
expect(page).to have_content "5 minutes ago"
expect(page).to have_content "20.6 KB"
expect(page).to have_content "created 4 days ago"
expect(page).to have_content "3 hours ago"
end
it "can delete cache entries" do
entry = create_cache_entry(key: "new-cache")
client = instance_double(Aws::S3::Client)
expect(Aws::S3::Client).to receive(:new).and_return(client)
expect(client).to receive(:delete_object).with(bucket: repository.bucket_name, key: entry.blob_key)
visit "#{project.path}/github/cache"
expect(page.status_code).to eq(200)
expect(page).to have_content entry.key
btn = find "#entry-#{entry.ubid} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(page.status_code).to eq(204)
visit "#{project.path}/github/cache"
expect(page).to have_flash_notice("Cache '#{entry.key}' deleted.")
end
it "raises not found when cache entry not exists" do
visit "#{project.path}/github/cache/etn0h8p5js1a4kpa9er7jkg77c"
expect(page.title).to eq("Ubicloud - ResourceNotFound")
expect(page.status_code).to eq(404)
expect(page).to have_content "ResourceNotFound"
end
end
end