Files
ubicloud/spec/routes/web/project/github_spec.rb

335 lines
14 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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(installation_id: 123, name: "test-user", type: "User", project_id: project.id, created_at: Time.now - 10 * 24 * 60 * 60) }
let(:repository) { GithubRepository.create(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
it "redirects to the first installation if it exists" do
installation
visit "#{project.path}/github"
expect(page.status_code).to eq(200)
expect(page).to have_current_path("#{project.path}/github/#{installation.ubid}/runner")
expect(page.title).to eq("Ubicloud - Active Runners")
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(described_class).to receive(:authorized_project).with(user, project.id).and_return(project)
expect(Config).to receive(:stripe_secret_key).and_return("secret_key").at_least(:once)
visit "#{project.path}/github/create"
expect(page.status_code).to eq(400)
expect(page.title).to eq("Ubicloud - GitHub Runners Integration")
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(described_class).to receive(:authorized_project).with(user, project.id).and_return(project).thrice
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 switch between installations" do
ins1 = GithubInstallation.create(installation_id: 111, name: "test-user", type: "User", project_id: project.id)
ins2 = GithubInstallation.create(installation_id: 222, name: "test-org", type: "Organization", project_id: project.id)
visit "#{project.path}/github/#{ins1.ubid}/runner"
click_link "test-org"
expect(page.status_code).to eq(200)
expect(page).to have_current_path("#{project.path}/github/#{ins2.ubid}/runner", ignore_query: true)
expect(page.title).to eq("Ubicloud - Active Runners")
click_link "test-user"
expect(page.status_code).to eq(200)
expect(page).to have_current_path("#{project.path}/github/#{ins1.ubid}/runner", ignore_query: true)
expect(page.title).to eq("Ubicloud - Active Runners")
end
it "enables premium runners for installation" do
installation.update(allocator_preferences: {})
expect(installation.premium_runner_enabled?).to be false
visit "#{project.path}/github/#{installation.ubid}/setting"
within("form#premium_runner_enabled_toggle") do
_csrf = find("input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/#{installation.ubid}", {premium_runner_enabled: true, _csrf:}
end
expect(page.status_code).to eq(302)
expect(installation.reload.premium_runner_enabled?).to be true
end
it "disables premium runners for installation" do
installation.update(allocator_preferences: {"family_filter" => ["standard", "premium"]})
expect(installation.premium_runner_enabled?).to be true
visit "#{project.path}/github/#{installation.ubid}/setting"
within("form#premium_runner_enabled_toggle") do
_csrf = find("input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/#{installation.ubid}", {premium_runner_enabled: false, _csrf:}
end
expect(page.status_code).to eq(302)
expect(installation.reload.premium_runner_enabled?).to be false
end
it "shows badge for free premium runner upgrade" do
installation.update(created_at: Time.now)
visit "#{project.path}/github/#{installation.ubid}/setting"
expect(page.status_code).to eq(200)
expect(page).to have_content "Youre eligible for an exclusive 50% off premium runners"
end
it "enables cache for installation" do
installation.update(cache_enabled: false)
expect(installation.cache_enabled).to be false
visit "#{project.path}/github/#{installation.ubid}/setting"
within("form#cache_enabled_toggle") do
_csrf = find("input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/#{installation.ubid}", {cache_enabled: true, _csrf:}
end
expect(page.status_code).to eq(302)
expect(installation.reload.cache_enabled).to be true
end
it "handles case where installation does not exist" do
installation.update(cache_enabled: false)
visit "#{project.path}/github/#{installation.ubid}/setting"
within("form#cache_enabled_toggle") do
_csrf = find("input[name='_csrf']", visible: false).value
installation.destroy
page.driver.post "#{project.path}/github/#{installation.ubid}", {cache_enabled: true, _csrf:}
end
expect(page.status_code).to eq(404)
end
it "disables cache for installation" do
installation.update(cache_enabled: true)
expect(installation.cache_enabled).to be true
visit "#{project.path}/github/#{installation.ubid}/setting"
within("form#cache_enabled_toggle") do
_csrf = find("input[name='_csrf']", visible: false).value
page.driver.post "#{project.path}/github/#{installation.ubid}", {cache_enabled: false, _csrf:}
end
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/invalid_id"
expect(page.status_code).to eq(404)
end
end
describe "runner" do
it "shows no active runner page" do
visit "#{project.path}/github/#{installation.ubid}/runner"
expect(page.status_code).to eq(200)
expect(page).to have_content "No active runners"
expect(page).to have_no_content "Youre eligible for an exclusive 50% off premium runners"
end
it "can list active runners" do
now = Time.now
expect(Time).to receive(:now).and_return(now).at_least(:once)
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-standard-4-ubuntu-2404", repository_name: "my-repo").subject.update(
created_at: now + 20,
ready_at: now - 50,
runner_id: 2,
vm_id: Prog::Vm::Nexus.assemble("dummy-public key", project.id, name: "runner-vm", size: "premium-4", location_id: Location::GITHUB_RUNNERS_ID).id,
workflow_job: {
"id" => 123,
"name" => "test-job",
"run_id" => 456,
"workflow_name" => "test-workflow",
"created_at" => (now - 60).iso8601,
"started_at" => (now - 40).iso8601
}
)
runner_waiting_job = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").subject.update(ready_at: now - 400, created_at: now)
runner_not_created = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud-arm", repository_name: "my-repo").subject.update(created_at: now - 38)
runner_concurrency_limit = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud-gpu", repository_name: "my-repo").update(label: "wait_concurrency_limit").subject.update(created_at: now - 3.68 * 60 * 60)
visit "#{project.path}/github/#{installation.ubid}/runner"
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - Active Runners")
expect(page).to have_no_content runner_deleted.ubid
displayed_runner_rows = page.all("table.min-w-full tbody tr").map { |row| row.all("td").map(&:text) }
expect(displayed_runner_rows).to eq [
["my-repo", "#{runner_with_job.ubid}\n4 vCPU\npremium\nx64\nubuntu-24", "test-workflow\ntest-job", "Running for 40s\nStarted in 20s", ""],
["my-repo", "#{runner_waiting_job.ubid}\n2 vCPU\nstandard\nx64\nubuntu-22", "Waiting for GitHub to assign a job\nReady for 6m 40s", "", ""],
["my-repo", "#{runner_not_created.ubid}\n2 vCPU\nstandard\narm64\nubuntu-22", "Provisioning an ephemeral virtual machine\nWaiting for 38s", "", ""],
["my-repo", "#{runner_concurrency_limit.ubid}\n6 vCPU\nstandard-gpu\nx64\nubuntu-22", "Reached your concurrency limit\nWaiting for 3h 40m 48s", "", ""]
]
end
it "can terminate runner" do
runner = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").subject
visit "#{project.path}/github/#{installation.ubid}/runner"
expect(page.status_code).to eq(200)
expect(page).to have_content runner.ubid
btn = find "#runner-#{runner.ubid} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(page.status_code).to eq(204)
visit "#{project.path}/github/#{installation.ubid}/runner"
expect(page).to have_flash_notice("Runner '#{runner.ubid}' forcibly terminated")
end
it "raises not found when runner not exists" do
runner = Prog::Vm::GithubRunner.assemble(installation, label: "ubicloud", repository_name: "my-repo").subject
visit "#{project.path}/github/#{installation.ubid}/runner"
runner.destroy
btn = find "#runner-#{runner.ubid} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(page.status_code).to eq(404)
end
it "shows badge for free premium runner upgrade" do
installation.update(created_at: Time.now)
visit "#{project.path}/github/#{installation.ubid}/runner"
expect(page.status_code).to eq(200)
expect(page).to have_content "Youre eligible for an exclusive 50% off premium runners"
find("a", text: /^Youre eligible/).click
expect(page.status_code).to eq(200)
expect(page.title).to eq("Ubicloud - GitHub Runner Settings")
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/#{installation.ubid}/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/#{installation.ubid}/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/#{installation.ubid}/cache"
expect(page).to have_flash_notice("Cache '#{entry.key}' deleted.")
end
it "raises not found when cache entry not exists" 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/#{installation.ubid}/cache"
entry.destroy
btn = find "#entry-#{entry.ubid} .delete-btn"
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
expect(page.status_code).to eq(404)
end
end
end