">
+
">
<%= response_template.title %>
diff --git a/app/views/users/_sidebar_additional.html.erb b/app/views/users/_sidebar_additional.html.erb
index ca60e4743..d52d6ae97 100644
--- a/app/views/users/_sidebar_additional.html.erb
+++ b/app/views/users/_sidebar_additional.html.erb
@@ -1,28 +1,10 @@
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 459941bec..f3f22aa55 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -206,7 +206,7 @@
<% cache "user-profile-sidebar-additional-#{@user.id}-#{@user.github_repos_updated_at}-#{@user.badge_achievements_count}-#{@user.organization_info_updated_at}", expires_in: 2.days do %>
- <%= render "users/sidebar_additional" %>
+ <%= render partial: "users/sidebar_additional", locals: { repositories: @github_repositories } %>
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 62b23b9ef..794feee34 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -195,7 +195,7 @@ Rails.application.routes.draw do
end
resources :stripe_active_cards, only: %i[create update destroy]
resources :live_articles, only: [:index]
- resources :github_repos, only: %i[index create update] do
+ resources :github_repos, only: %i[index] do
collection do
post "/update_or_create", to: "github_repos#update_or_create"
end
diff --git a/spec/models/github_repo_spec.rb b/spec/models/github_repo_spec.rb
index 5f4c83a2b..b9cac7d5b 100644
--- a/spec/models/github_repo_spec.rb
+++ b/spec/models/github_repo_spec.rb
@@ -2,34 +2,79 @@ require "rails_helper"
RSpec.describe GithubRepo, type: :model do
let(:user) { create(:user, :with_identity, identities: ["github"]) }
- let(:repo) { build(:github_repo, user_id: user.id) }
+ let(:repo) { create(:github_repo, user: user) }
- before { omniauth_mock_github_payload }
-
- it { is_expected.to validate_presence_of(:name) }
- it { is_expected.to validate_presence_of(:url) }
- it { is_expected.to validate_uniqueness_of(:url) }
- it { is_expected.to validate_uniqueness_of(:github_id_code) }
-
- it "saves" do
- repo.save
- expect(repo).to be_valid
+ before do
+ omniauth_mock_github_payload
end
- describe "::find_or_create" do
- it "creates a new object if one doesn't already exists" do
- params = { name: Faker::Book.title, user_id: user.id, github_id_code: rand(1000),
- url: Faker::Internet.url }
- described_class.find_or_create(params)
- expect(described_class.count).to eq(1)
+ describe "validations" do
+ describe "builtin validations" do
+ subject { repo }
+
+ it { is_expected.to validate_presence_of(:github_id_code) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_uniqueness_of(:github_id_code) }
+ it { is_expected.to validate_uniqueness_of(:url) }
+ it { is_expected.to validate_url_of(:url) }
+ end
+ end
+
+ context "when callbacks are triggered after save" do
+ let(:repo) { build(:github_repo, user: user) }
+
+ describe "clearing caches" do
+ it "updates the user's updated_at" do
+ old_updated_at = user.updated_at
+
+ Timecop.travel(1.minute.from_now) do
+ repo.save
+ end
+
+ expect(user.reload.updated_at.to_i > old_updated_at.to_i).to be(true)
+ end
+
+ it "busts the correct caches" do
+ allow(CacheBuster).to receive(:bust)
+
+ repo.save
+
+ expect(CacheBuster).to have_received(:bust).with(user.path)
+ expect(CacheBuster).to have_received(:bust).with("#{user.path}?i=i")
+ expect(CacheBuster).to have_received(:bust).with("#{user.path}/?i=i")
+ end
+ end
+ end
+
+ describe ".upsert" do
+ let(:params) do
+ {
+ github_id_code: rand(1000),
+ name: Faker::Book.title,
+ url: Faker::Internet.url
+ }
end
- it "returns existing object if it already exists" do
- repo.save
- before_update_id = repo.id
- params = { github_id_code: repo.github_id_code }
- updated_repo = described_class.find_or_create(params)
- expect(updated_repo.id).to eq(before_update_id)
+ it "creates a new repo" do
+ expect do
+ described_class.upsert(user, params)
+ end.to change(described_class, :count).by(1)
+ end
+
+ it "creates a repo for the given user" do
+ repo = described_class.upsert(user, params)
+
+ expect(repo.user_id).to eq(user.id)
+ end
+
+ it "returns an existing repo updated with new params" do
+ new_name = Faker::Book.title
+
+ new_repo = described_class.upsert(user, url: repo.url, name: new_name)
+
+ expect(new_repo.id).to eq(repo.id)
+ expect(repo.reload.name).to eq(new_name)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b80be8fbf..f251f9877 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1123,4 +1123,28 @@ RSpec.describe User, type: :model do
expect(described_class.mascot_account).to eq(user)
end
end
+
+ describe "#authenticated_through?" do
+ let(:provider) { Authentication::Providers.available.first }
+
+ it "returns false if provider is not known" do
+ expect(user.authenticated_through?(:unknown)).to be(false)
+ end
+
+ it "returns false if provider is not enabled" do
+ providers = Authentication::Providers.available - [provider]
+ allow(Authentication::Providers).to receive(:enabled).and_return(providers)
+
+ expect(user.authenticated_through?(provider)).to be(false)
+ end
+
+ it "returns false if the user has no related identity" do
+ expect(user.authenticated_through?(provider)).to be(false)
+ end
+
+ it "returns true if the user has related identity" do
+ user = create(:user, :with_identity, identities: [provider])
+ expect(user.authenticated_through?(provider)).to be(true)
+ end
+ end
end
diff --git a/spec/policies/github_repo_policy_spec.rb b/spec/policies/github_repo_policy_spec.rb
index eae4204f8..097222d79 100644
--- a/spec/policies/github_repo_policy_spec.rb
+++ b/spec/policies/github_repo_policy_spec.rb
@@ -3,37 +3,35 @@ require "rails_helper"
RSpec.describe GithubRepoPolicy, type: :policy do
subject { described_class.new(user, github_repo) }
- let_it_be(:github_repo) { create(:github_repo) }
- let(:valid_attributes) { %i[github_id_code] }
-
context "when user is not signed in" do
let(:user) { nil }
+ let(:github_repo) { build(:github_repo, user: user) }
it { within_block_is_expected.to raise_error(Pundit::NotAuthorizedError) }
end
- context "when user is not the owner" do
- let!(:user) { create(:user) }
+ context "when the user is not authenticated through GitHub" do
+ let(:user) { build(:user) }
+ let(:github_repo) { build(:github_repo, user: user) }
- it { is_expected.to permit_actions(%i[create]) }
- it { is_expected.to forbid_actions(%i[update]) }
-
- context "when user is banned" do
- before { user.add_role(:banned) }
-
- it { is_expected.to forbid_actions(%i[create update]) }
- end
+ it { is_expected.to forbid_actions(%i[index update_or_create]) }
end
- context "when user is the owner" do
- let(:user) { github_repo.user }
+ context "when the user is authenticated through GitHub" do
+ let(:user) { create(:user, :with_identity, identities: %i[github]) }
+ let(:github_repo) { build(:github_repo, user: user) }
- it { is_expected.to permit_actions(%i[create update]) }
-
- context "when user is banned" do
- let(:user) { build(:user, :banned) }
-
- it { is_expected.to forbid_actions(%i[create update]) }
+ before do
+ omniauth_mock_github_payload
end
+
+ it { is_expected.to permit_actions(%i[index update_or_create]) }
+ end
+
+ context "when user is banned" do
+ let(:user) { build(:user, :banned) }
+ let(:github_repo) { build(:github_repo, user: user) }
+
+ it { is_expected.to forbid_actions(%i[index update_or_create]) }
end
end
diff --git a/spec/requests/github_repos_spec.rb b/spec/requests/github_repos_spec.rb
index 22ae06456..14499fe91 100644
--- a/spec/requests/github_repos_spec.rb
+++ b/spec/requests/github_repos_spec.rb
@@ -2,13 +2,14 @@ require "rails_helper"
RSpec.describe "GithubRepos", type: :request do
let(:user) { create(:user, :with_identity, identities: ["github"]) }
- let(:repo) { build(:github_repo, user_id: user.id) }
+ let(:repo) { build(:github_repo, user: user) }
let(:my_octokit_client) { instance_double(Octokit::Client) }
let(:stubbed_github_repos) do
repo_params = repo.attributes.merge(
id: repo.github_id_code,
html_url: Faker::Internet.url,
)
+
[OpenStruct.new(repo_params)]
end
let(:headers) do
@@ -20,82 +21,58 @@ RSpec.describe "GithubRepos", type: :request do
before do
omniauth_mock_github_payload
+
allow(Octokit::Client).to receive(:new).and_return(my_octokit_client)
allow(my_octokit_client).to receive(:repositories) { stubbed_github_repos }
+ allow(my_octokit_client).to receive(:repository) { stubbed_github_repos.first }
end
describe "GET /github_repos" do
context "when user is unauthorized" do
- it "returns unauthorized" do
+ it "returns unauthorized if the user is not signed in" do
get github_repos_path, headers: headers
expect(response).to have_http_status(:unauthorized)
end
+
+ it "returns unauthorized if the user not has authenticated through GitHub" do
+ user = create(:user)
+ sign_in user
+
+ expect do
+ get github_repos_path, headers: headers
+ end.to raise_error(Pundit::NotAuthorizedError)
+ end
end
context "when user is authorized" do
before { sign_in user }
+ it "returns unauthorized if the user is not authorized to perform the GitHub API call" do
+ allow(Octokit::Client).to receive(:new).and_raise(Octokit::Unauthorized)
+
+ get github_repos_path, headers: headers
+ expect(response).to have_http_status(:unauthorized)
+ expect(response.parsed_body["error"]).to include("GitHub Unauthorized")
+ end
+
it "returns 200 on success" do
get github_repos_path, headers: headers
expect(response).to have_http_status(:ok)
end
- it "returns 401 if github raises an unauthorized error" do
- allow(Octokit::Client).to receive(:new).and_raise(Octokit::Unauthorized)
-
- get github_repos_path, headers: headers
- expect(response).to have_http_status(:unauthorized)
- expect(response.parsed_body["error"]).to include("Github Unauthorized")
- end
-
- it "returns repos with the correct json representation" do
+ it "returns repositories with the correct JSON representation" do
get github_repos_path, headers: headers
response_repo = response.parsed_body.first
expect(response_repo["name"]).to eq(repo.name)
expect(response_repo["fork"]).to eq(repo.fork)
- expect(response_repo["selected"]).to be(false)
+ expect(response_repo["featured"]).to be(false)
end
end
end
- describe "POST /github_repos" do
- before { sign_in user }
-
- it "returns a 302" do
- params = { github_repo: { github_id_code: repo.github_id_code } }
- post github_repos_path, params: params
-
- expect(response).to have_http_status(:found)
- end
-
- it "creates a new GithubRepo object" do
- params = { github_repo: { github_id_code: repo.github_id_code } }
- expect do
- post github_repos_path, params: params
- end.to change(GithubRepo, :count).by(1)
- end
- end
-
- describe "PUT /github_repos/:id" do
- before do
- repo.save
- sign_in user
- end
-
- it "returns a 302" do
- put "/github_repos/#{repo.id}"
- expect(response).to have_http_status(:found)
- end
-
- it "unfeatures the requested GithubRepo" do
- put "/github_repos/#{repo.id}"
- expect(GithubRepo.first.featured).to eq(false)
- end
- end
-
describe "POST /github_repos/update_or_create" do
before { sign_in user }
@@ -109,13 +86,12 @@ RSpec.describe "GithubRepos", type: :request do
expect(response.content_type).to eq("application/json")
end
- it "returns 404 and json response on error" do
- allow(my_octokit_client).to receive(:repositories).and_return([])
+ it "returns 404 if no repository is found" do
+ allow(my_octokit_client).to receive(:repository).and_raise(Octokit::NotFound)
- params = { github_repo: "{}" }
+ params = { github_repo: github_repo.to_json }
post update_or_create_github_repos_path(params), headers: headers
expect(response).to have_http_status(:not_found)
- expect(response.body).to include("Could not find Github repo")
end
it "updates the current user github_repos_updated_at" do
@@ -124,7 +100,7 @@ RSpec.describe "GithubRepos", type: :request do
Timecop.travel(5.minutes.from_now) do
params = { github_repo: github_repo.to_json }
post update_or_create_github_repos_path(params), headers: headers
- expect(user.reload.github_repos_updated_at > previous_date).to be(true)
+ expect(user.reload.github_repos_updated_at.to_i > previous_date.to_i).to be(true)
end
end
diff --git a/spec/requests/internal/negative_reactions_spec.rb b/spec/requests/internal/negative_reactions_spec.rb
index d5e7a1509..f19bc5903 100644
--- a/spec/requests/internal/negative_reactions_spec.rb
+++ b/spec/requests/internal/negative_reactions_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe "/internal/negative_reactions", type: :request do
get "/internal/negative_reactions"
expect(response.body).to include(moderator.username)
expect(response.body).to include(user_reaction.reactable.username)
- expect(response.body).to include(article_reaction.reactable.title)
+ expect(response.body).to include(CGI.escapeHTML(article_reaction.reactable.title))
end
end
end
diff --git a/spec/requests/user/user_profile_spec.rb b/spec/requests/user/user_profile_spec.rb
index 95166c225..22ef2a221 100644
--- a/spec/requests/user/user_profile_spec.rb
+++ b/spec/requests/user/user_profile_spec.rb
@@ -128,20 +128,40 @@ RSpec.describe "UserProfiles", type: :request do
end
end
- context "when github repo" do
- before do
- repo = build(:github_repo, user: user)
- params = { name: Faker::Book.title, user_id: user.id, github_id_code: repo.github_id_code,
- url: Faker::Internet.url, description: "A book bot :robot:", featured: true,
- stargazers_count: 1 }
- updated_repo = GithubRepo.find_or_create(params)
-
- user.github_repos = [updated_repo]
+ context "when displaying a GitHub repository on the profile" do
+ let(:github_user) { create(:user, :with_identity, identities: %i[github]) }
+ let(:params) do
+ {
+ description: "A book bot :robot:",
+ featured: true,
+ github_id_code: build(:github_repo).github_id_code,
+ name: Faker::Book.title,
+ stargazers_count: 1,
+ url: Faker::Internet.url
+ }
end
- it "renders emoji in description of pinned github repo" do
- get "/#{user.username}"
- expect(response.body).to include "A book bot 🤖"
+ before do
+ omniauth_mock_github_payload
+ end
+
+ it "renders emoji in description of featured repository" do
+ GithubRepo.upsert(github_user, params)
+
+ get "/#{github_user.username}"
+ expect(response.body).to include("A book bot 🤖")
+ end
+
+ it "does not show a non featured repository" do
+ GithubRepo.upsert(github_user, params.merge(featured: false))
+
+ get "/#{github_user.username}"
+ expect(response.body).not_to include("A book bot 🤖")
+ end
+
+ it "does not render anything if the user has not authenticated through GitHub" do
+ get "/#{github_user.username}"
+ expect(response.body).not_to include("github-repos-container")
end
end
end
diff --git a/spec/requests/user/user_settings_spec.rb b/spec/requests/user/user_settings_spec.rb
index 7b2771c76..ef18fefef 100644
--- a/spec/requests/user/user_settings_spec.rb
+++ b/spec/requests/user/user_settings_spec.rb
@@ -150,6 +150,23 @@ RSpec.describe "UserSettings", type: :request do
expect(response.body).to include("Connect GitHub Account")
end
end
+
+ describe ":integrations" do
+ it "renders the repositories container if the user has authenticated through GitHub" do
+ user = create(:user, :with_identity, identities: [:github])
+ sign_in user
+
+ get user_settings_path(tab: :integrations)
+ expect(response.body).to include("github-repos-container")
+ end
+
+ it "does not render anything if the user has not authenticated through GitHub" do
+ sign_in user
+
+ get user_settings_path(tab: :integrations)
+ expect(response.body).not_to include("github-repos-container")
+ end
+ end
end
describe "PUT /update/:id" do