Merge branch 'feature/interview-voting' into develop
This commit is contained in:
commit
85bb4d4368
@ -75,7 +75,7 @@ guard :shell, all_on_start: true do
|
||||
# TODO: Annoyingly, all files are linted twice on start/full runs. Why?
|
||||
watch %r{app/assets/javascripts/*/.*} do |file|
|
||||
system %(echo "ESLint:\033[32m #{file[0]}\033[0m")
|
||||
system %(eslint #{file[0]})
|
||||
system %(./node_modules/eslint/bin/eslint.js #{file[0]})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -77,10 +77,14 @@ There are some convenience scripts included to make starting the container and r
|
||||
* `./start-server.sh`
|
||||
- starts up just rails server for viewing application
|
||||
|
||||
## Deploying a new version
|
||||
* ssh into server
|
||||
* cd into app root
|
||||
* run deploy.sh
|
||||
|
||||
## TODOs and notes
|
||||
|
||||
* Question attachment path: http://dev.perficientxd.com/skill_assets/
|
||||
* Question attachment path: https://dev.perficientdigital.com/skills-app-images/
|
||||
* clean code
|
||||
* [Confident Ruby](http://www.confidentruby.com/)
|
||||
* [POODR](http://www.poodr.com/)
|
||||
|
@ -1,7 +1,8 @@
|
||||
function handleAjaxResponse($el) {
|
||||
function handleAjaxResponse($el, callback) {
|
||||
var $header = $('header');
|
||||
$el.on("ajax:success", function(e, data){
|
||||
$header.after('<div class="success">' + data.message + '</div>');
|
||||
callback(data);
|
||||
}).on("ajax:error", function(e, xhr) {
|
||||
if (xhr.status === 400){
|
||||
$header.after('<div class="error">' + xhr.responseJSON.join('<br>') + '</div>');
|
||||
@ -11,6 +12,25 @@ function handleAjaxResponse($el) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateVotes(data){
|
||||
$("[data-id=up-votes]").html(data.upCount);
|
||||
$("[data-id=down-votes]").html(data.downCount);
|
||||
$("[data-id=my-vote]").html(data.myVote);
|
||||
}
|
||||
|
||||
function updateVeto(data){
|
||||
$("[data-id=interview-request]").html(data.requestCopy);
|
||||
$("[data-id=interview-decline]").html(data.declineCopy);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-id=ajax-action]').each(function(){ handleAjaxResponse($(this)); });
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-id=vote-count]').each(function(){ handleAjaxResponse($(this), updateVotes); });
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
$('[data-id=veto-status]').each(function(){ handleAjaxResponse($(this), updateVeto); });
|
||||
});
|
||||
|
12
app/assets/stylesheets/molecules/_review_meta.scss
Normal file
12
app/assets/stylesheets/molecules/_review_meta.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.review_meta {
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
display: flex;
|
||||
& > div { flex: 1 1 auto; }
|
||||
}
|
||||
|
||||
.review_meta__votes,
|
||||
.review_meta__vetos {
|
||||
a { padding: 5px; }
|
||||
}
|
||||
}
|
58
app/controllers/admin/vote_controller.rb
Normal file
58
app/controllers/admin/vote_controller.rb
Normal file
@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
module Admin
|
||||
class VoteController < AdminController
|
||||
def up
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.cast_yea_on(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Vote Counted",
|
||||
upCount: @candidate.votes.yea.count,
|
||||
downCount: @candidate.votes.nay.count,
|
||||
myVote: "yea"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
|
||||
def down
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.cast_nay_on(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Vote Counted",
|
||||
upCount: @candidate.votes.yea.count,
|
||||
downCount: @candidate.votes.nay.count,
|
||||
myVote: "nay"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
|
||||
def approve
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.approve_candidate(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Interview requested!",
|
||||
requestCopy: "Requested",
|
||||
declineCopy: "Decline Interview"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
|
||||
def decline
|
||||
@candidate = Candidate.find_by(test_hash: params[:test_hash])
|
||||
authorize ReviewerVote.find_by(user_id: current_user.id, candidate_id: @candidate.id)
|
||||
current_user.decline_candidate(@candidate)
|
||||
|
||||
results = {
|
||||
message: "Interview declined.",
|
||||
requestCopy: "Request Interview",
|
||||
declineCopy: "Declined"
|
||||
}
|
||||
render json: results.to_json
|
||||
end
|
||||
end
|
||||
end
|
@ -36,6 +36,7 @@ class QuizController < ApplicationController
|
||||
|
||||
def complete_and_email
|
||||
if current_candidate.update_attributes(completed: true)
|
||||
current_candidate.build_reviews
|
||||
CandidateMailer.submitted(current_candidate).deliver_later
|
||||
RecruiterMailer.candidate_submitted(current_candidate).deliver_later
|
||||
ReviewerMailer.candidate_submission(current_candidate).deliver_later
|
||||
|
@ -4,6 +4,8 @@ class Candidate < ApplicationRecord
|
||||
has_many :questions, -> { order("sort") }, through: :quiz
|
||||
has_many :answers
|
||||
belongs_to :recruiter, class_name: "User"
|
||||
has_many :votes, class_name: "ReviewerVote"
|
||||
has_many :reviewers, through: :quiz
|
||||
|
||||
serialize :email, CryptSerializer
|
||||
|
||||
@ -15,6 +17,18 @@ class Candidate < ApplicationRecord
|
||||
validates :email, uniqueness: true, presence: true, email_format: true
|
||||
validates :test_hash, uniqueness: true, presence: true
|
||||
|
||||
enum review_status: {
|
||||
pending: 0,
|
||||
approved: 1,
|
||||
declined: 2
|
||||
}
|
||||
|
||||
def build_reviews
|
||||
reviewers.each do |reviewer|
|
||||
votes.find_or_create_by(user_id: reviewer.id)
|
||||
end
|
||||
end
|
||||
|
||||
def submitted_answers
|
||||
answers.where(submitted: true)
|
||||
end
|
||||
|
18
app/models/reviewer_vote.rb
Normal file
18
app/models/reviewer_vote.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
class ReviewerVote < ApplicationRecord
|
||||
belongs_to :candidate
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, uniqueness: { scope: :candidate_id }
|
||||
|
||||
enum vote: {
|
||||
undecided: 0,
|
||||
yea: 1,
|
||||
nay: 2
|
||||
}
|
||||
|
||||
enum veto: {
|
||||
approved: 1,
|
||||
rejected: 2
|
||||
}
|
||||
end
|
@ -4,6 +4,9 @@ class User < ApplicationRecord
|
||||
has_many :candidates, foreign_key: :recruiter_id
|
||||
has_many :reviewer_to_quizzes
|
||||
has_many :quizzes, through: :reviewer_to_quizzes
|
||||
has_many :votes, class_name: 'ReviewerVote'
|
||||
|
||||
has_many :reviewees, through: :quizzes, source: :candidates
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validates :name, presence: true
|
||||
@ -15,6 +18,42 @@ class User < ApplicationRecord
|
||||
save
|
||||
end
|
||||
|
||||
# Voting
|
||||
def cast_yea_on candidate
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.vote = :yea
|
||||
vote.save
|
||||
end
|
||||
|
||||
def cast_nay_on candidate
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.vote = :nay
|
||||
vote.save
|
||||
end
|
||||
|
||||
def approve_candidate candidate
|
||||
candidate = Candidate.find(candidate.to_i)
|
||||
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.veto = :approved
|
||||
candidate.update_attribute(:review_status, :approved) if vote.save
|
||||
end
|
||||
|
||||
def decline_candidate candidate
|
||||
candidate = Candidate.find(candidate.to_i)
|
||||
|
||||
vote = votes.find_by(candidate_id: candidate.to_i)
|
||||
vote.veto = :rejected
|
||||
candidate.update_attribute(:review_status, :declined) if vote.save
|
||||
end
|
||||
|
||||
def my_vote candidate
|
||||
candidate = Candidate.find(candidate.to_i)
|
||||
|
||||
my_vote = votes.find_by(candidate_id: candidate.id)
|
||||
my_vote.vote unless my_vote.nil?
|
||||
end
|
||||
|
||||
# Roles
|
||||
def admin?
|
||||
'admin' == role
|
||||
@ -45,7 +84,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def acts_as_reviewer?
|
||||
%w(admin reviewer).include? role
|
||||
%w(admin manager reviewer).include? role
|
||||
end
|
||||
|
||||
private
|
||||
|
45
app/policies/reviewer_vote_policy.rb
Normal file
45
app/policies/reviewer_vote_policy.rb
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
class ReviewerVotePolicy < ApplicationPolicy
|
||||
# Voting Policy
|
||||
#
|
||||
# Only Reviewers, Managers, and Admins, can cast a vote on a quiz result
|
||||
#
|
||||
# Reviewers can vote any quiz they are linked to
|
||||
# Only Managers, and Admins, can veto a quiz result
|
||||
|
||||
def up?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
def down?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
def approve?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_manager?
|
||||
end
|
||||
|
||||
def decline?
|
||||
return true if user.acts_as_admin?
|
||||
return false unless record.candidate.reviewers.include? user
|
||||
user.acts_as_manager?
|
||||
end
|
||||
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
return ReviewerVote.none if user.recruiter?
|
||||
|
||||
if user.reviewer?
|
||||
scope.where(user_id: user.id)
|
||||
else
|
||||
scope
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -15,6 +15,7 @@
|
||||
<th>Progress</th>
|
||||
<th>Completed</th>
|
||||
<th>Reminded</th>
|
||||
<th>Review Status</th>
|
||||
</tr>
|
||||
|
||||
<% @candidates.each do |candidate| %>
|
||||
@ -28,8 +29,9 @@
|
||||
</td>
|
||||
<td><%= candidate.experience %> years</td>
|
||||
<td><%= candidate.status %></td>
|
||||
<td><%= candidate.completed ? "Submitted" : "" %></td>
|
||||
<td><%= candidate.completed ? link_to("Submitted", admin_result_path(candidate.test_hash)) : "" %></td>
|
||||
<td><%= candidate.reminded ? "Yes" : "" %></td>
|
||||
<td><%= candidate.review_status %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
34
app/views/admin/result/_voting.html.erb
Normal file
34
app/views/admin/result/_voting.html.erb
Normal file
@ -0,0 +1,34 @@
|
||||
<% # TODO: This needs to be extracted into a decorator, or something. It is only a quick hack solution. %>
|
||||
|
||||
<% if current_user.acts_as_reviewer? %>
|
||||
<div class="review_meta__votes" data-id="vote-count">
|
||||
<strong>Votes: </strong>
|
||||
<%= link_to admin_up_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Yea (<span data-id="up-votes"><%= @candidate.votes.yea.count %></span>)
|
||||
<% end %>
|
||||
<%= link_to admin_down_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
Nay (<span data-id="down-votes"><%= @candidate.votes.nay.count %></span>)
|
||||
<% end %>
|
||||
<small>(Your vote: <span data-id="my-vote"><%= current_user.my_vote(@candidate) %></span>)</small>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if current_user.acts_as_manager? %>
|
||||
<div class="review_meta__vetos" data-id="veto-status">
|
||||
<strong>Manager Vetos: </strong>
|
||||
<%= link_to admin_approve_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
<span data-id="interview-request">
|
||||
<%= @candidate.approved? ? "Requested" : "Request Interview" %>
|
||||
</span>
|
||||
<% end %>
|
||||
<%= link_to admin_decline_vote_path(test_hash: @candidate.test_hash), remote: true do %>
|
||||
<span data-id="interview-decline">
|
||||
<%= @candidate.declined? ? "Declined" : "Decline Interview" %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% else %>
|
||||
|
||||
<strong>Candidate Interview Status: </strong><%= @candidate.review_status %>
|
||||
<% end %>
|
@ -1,10 +1,15 @@
|
||||
<main class="summary_tpl">
|
||||
<h2 class="prft-heading">Quiz Review</h2>
|
||||
<p>
|
||||
<strong>Test ID:</strong> <%= @candidate.test_hash %><br />
|
||||
<strong>Years of Experience:</strong> <%= @candidate.experience %><br />
|
||||
<strong>Recruiter Email:</strong> <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %><br />
|
||||
</p>
|
||||
|
||||
<div class="review_meta">
|
||||
<div>
|
||||
<strong>Test ID:</strong> <%= @candidate.test_hash %><br />
|
||||
<strong>Years of Experience:</strong> <%= @candidate.experience %><br />
|
||||
<strong>Recruiter Email:</strong> <%= mail_to @candidate.recruiter.name, @candidate.recruiter.email %><br />
|
||||
</div>
|
||||
|
||||
<div><%= render partial: 'voting' %></div>
|
||||
</div>
|
||||
|
||||
<% @quiz.each do |question| %>
|
||||
<%= form_for(:answer, url: '#never-post', html:{id: 'summary-form'}) do |form| %>
|
||||
|
@ -15,12 +15,20 @@
|
||||
<%= form.select :role, admin_role_options(user.role), include_blank: false %>
|
||||
</div>
|
||||
|
||||
<%= form.collection_check_boxes(:quiz_ids, Quiz.all, :id, :name, {}, {class: 'checkbox'}) do | quiz | %>
|
||||
<div class="form-group-multiples">
|
||||
<%= quiz.check_box( checked: user.quizzes.include?(quiz.object)) %>
|
||||
<%= quiz.label %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-group">
|
||||
<div><strong>Quiz Review List</strong></div>
|
||||
<p>
|
||||
Quizzes this user should be reviewing the results of.<br />
|
||||
Admins and Recruiters should not have any checked, unless they are expected
|
||||
to participate in the technical review for that quiz.
|
||||
</p>
|
||||
<%= form.collection_check_boxes(:quiz_ids, Quiz.all, :id, :name, {}, {class: 'checkbox'}) do | quiz | %>
|
||||
<div class="form-group-multiples">
|
||||
<%= quiz.check_box( checked: user.quizzes.include?(quiz.object)) %>
|
||||
<%= quiz.label %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= form.submit %>
|
||||
<% end %>
|
||||
|
@ -50,6 +50,11 @@ Rails.application.routes.draw do
|
||||
get "/admin/results", to: "admin/result#index", as: :admin_results
|
||||
get "/admin/result/:test_hash", to: "admin/result#view", as: :admin_result
|
||||
|
||||
get "admin/vote/:test_hash/up", to: "admin/vote#up", as: :admin_up_vote
|
||||
get "admin/vote/:test_hash/down", to: "admin/vote#down", as: :admin_down_vote
|
||||
get "admin/vote/:test_hash/approve", to: "admin/vote#approve", as: :admin_approve_vote
|
||||
get "admin/vote/:test_hash/decline", to: "admin/vote#decline", as: :admin_decline_vote
|
||||
|
||||
get "/admin", to: "admin/dashboard#show", as: :admin
|
||||
|
||||
#########################################################################################
|
||||
|
18
db/migrate/20161118023249_candidate_review_system.rb
Normal file
18
db/migrate/20161118023249_candidate_review_system.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
class CandidateReviewSystem < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :reviewer_votes do |t|
|
||||
t.integer :candidate_id
|
||||
t.integer :user_id
|
||||
t.integer :vote, default: 0, null: false
|
||||
t.integer :veto, default: 0, null: false
|
||||
t.datetime :last_reminded
|
||||
t.boolean :locked, default: false, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
add_index :reviewer_votes, [:candidate_id, :user_id], unique: true
|
||||
|
||||
add_column :candidates, :review_status, :integer, default: 0, null: false
|
||||
end
|
||||
end
|
6
db/migrate/20161120175737_init_reviewer_votes.rb
Normal file
6
db/migrate/20161120175737_init_reviewer_votes.rb
Normal file
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
class InitReviewerVotes < ActiveRecord::Migration[5.0]
|
||||
def up
|
||||
Candidate.where(completed: true).each(&:build_reviews)
|
||||
end
|
||||
end
|
19
db/schema.rb
19
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160915164450) do
|
||||
ActiveRecord::Schema.define(version: 20161120175737) do
|
||||
|
||||
create_table "answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.integer "candidate_id"
|
||||
@ -33,9 +33,10 @@ ActiveRecord::Schema.define(version: 20160915164450) do
|
||||
t.integer "recruiter_id"
|
||||
t.boolean "completed"
|
||||
t.boolean "reminded"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "quiz_id"
|
||||
t.integer "review_status", default: 0, null: false
|
||||
t.index ["quiz_id"], name: "index_candidates_on_quiz_id", using: :btree
|
||||
t.index ["recruiter_id"], name: "index_candidates_on_recruiter_id", using: :btree
|
||||
t.index ["test_hash"], name: "index_candidates_on_test_hash", unique: true, using: :btree
|
||||
@ -73,6 +74,18 @@ ActiveRecord::Schema.define(version: 20160915164450) do
|
||||
t.index ["quiz_id"], name: "index_reviewer_to_quizzes_on_quiz_id", using: :btree
|
||||
end
|
||||
|
||||
create_table "reviewer_votes", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.integer "candidate_id"
|
||||
t.integer "user_id"
|
||||
t.integer "vote", default: 0, null: false
|
||||
t.integer "veto", default: 0, null: false
|
||||
t.datetime "last_reminded"
|
||||
t.boolean "locked", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["candidate_id", "user_id"], name: "index_reviewer_votes_on_candidate_id_and_user_id", unique: true, using: :btree
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
|
||||
t.string "name"
|
||||
t.string "email"
|
||||
|
BIN
erd.pdf
BIN
erd.pdf
Binary file not shown.
@ -19,5 +19,19 @@ module Admin
|
||||
assert assigns(:quiz), "@quiz not present"
|
||||
assert assigns(:status), "@status not present"
|
||||
end
|
||||
|
||||
test "reviewer can view result for henry" do
|
||||
auth_reviewer
|
||||
|
||||
get admin_result_url(candidates(:henry).test_hash)
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "recruiter can view result for henry" do
|
||||
auth_user users(:recruiter)
|
||||
|
||||
get admin_result_url(candidates(:henry).test_hash)
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,4 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
# frozen_string_literal: true()
|
||||
require 'test_helper'
|
||||
|
||||
module Admin
|
||||
|
59
test/controllers/admin/vote_controller_test.rb
Normal file
59
test/controllers/admin/vote_controller_test.rb
Normal file
@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
module Admin
|
||||
class VoteControllerTest < ActionDispatch::IntegrationTest
|
||||
test "reviewer can up vote henry" do
|
||||
auth_user users(:reviewer)
|
||||
henry = candidates(:henry)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.yea.count", 1) do
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
end
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "reviewer can down vote henry" do
|
||||
auth_user users(:reviewer)
|
||||
henry = candidates(:henry)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.nay.count", 1) do
|
||||
get admin_down_vote_url(henry.test_hash)
|
||||
end
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "reviewer can change vote on henry" do
|
||||
auth_user users(:reviewer)
|
||||
henry = candidates(:henry)
|
||||
get admin_up_vote_url(henry.test_hash)
|
||||
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.yea.count", -1) do
|
||||
assert_difference("Candidate.find(#{henry.id}).votes.nay.count", 1) do
|
||||
get admin_down_vote_url(henry.test_hash)
|
||||
end
|
||||
end
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "manager can approve henry" do
|
||||
auth_user users(:manager)
|
||||
henry = candidates(:henry)
|
||||
get admin_approve_vote_url(henry.test_hash)
|
||||
|
||||
assert_equal 1, henry.votes.approved.count
|
||||
assert_equal 'approved', Candidate.find(henry.to_i).review_status
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "manager can decline henry" do
|
||||
auth_user users(:manager)
|
||||
henry = candidates(:henry)
|
||||
get admin_decline_vote_url(henry.test_hash)
|
||||
|
||||
assert_equal 1, henry.votes.rejected.count
|
||||
assert_equal 'declined', Candidate.find(henry.to_i).review_status
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
end
|
292
test/fixtures/answers.yml
vendored
292
test/fixtures/answers.yml
vendored
@ -404,3 +404,295 @@ juan10:
|
||||
created_at: <%= DateTime.now() - 38.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 38.hours - 20.minutes %>
|
||||
|
||||
stacy1:
|
||||
candidate: stacy
|
||||
question: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
answer: option 3
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
stacy2:
|
||||
candidate: stacy
|
||||
question: fed2
|
||||
answer: 'indexOf()'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
stacy3:
|
||||
candidate: stacy
|
||||
question: fed3
|
||||
answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
stacy4:
|
||||
candidate: stacy
|
||||
question: fed4
|
||||
answer: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
|
||||
stacy5:
|
||||
candidate: stacy
|
||||
question: fed5
|
||||
answer: 'Dynamic listeners'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
|
||||
stacy6:
|
||||
candidate: stacy
|
||||
question: fed6
|
||||
answer: Integer posuere erat a ante venenatis dapibus posuere velit aliquet.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 32.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 12.minutes %>
|
||||
|
||||
stacy7:
|
||||
candidate: stacy
|
||||
question: fed7
|
||||
answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 34.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 14.minutes %>
|
||||
|
||||
stacy8:
|
||||
candidate: stacy
|
||||
question: fed8
|
||||
answer:
|
||||
other: Some generic user input
|
||||
options:
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 36.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 16.minutes %>
|
||||
|
||||
stacy9:
|
||||
candidate: stacy
|
||||
question: fed9
|
||||
answer:
|
||||
other: Brunch
|
||||
options:
|
||||
- Neither
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 38.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 18.minutes %>
|
||||
|
||||
stacy10:
|
||||
candidate: stacy
|
||||
question: fed10
|
||||
answer: ["Live long and prosper", "Who you calling Scruffy?"]
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
|
||||
henry1:
|
||||
candidate: henry
|
||||
question: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
answer: option 3
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
henry2:
|
||||
candidate: henry
|
||||
question: fed2
|
||||
answer: 'indexOf()'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
henry3:
|
||||
candidate: henry
|
||||
question: fed3
|
||||
answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
henry4:
|
||||
candidate: henry
|
||||
question: fed4
|
||||
answer: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
|
||||
henry5:
|
||||
candidate: henry
|
||||
question: fed5
|
||||
answer: 'Dynamic listeners'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
|
||||
henry6:
|
||||
candidate: henry
|
||||
question: fed6
|
||||
answer: Integer posuere erat a ante venenatis dapibus posuere velit aliquet.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 32.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 12.minutes %>
|
||||
|
||||
henry7:
|
||||
candidate: henry
|
||||
question: fed7
|
||||
answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 34.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 14.minutes %>
|
||||
|
||||
henry8:
|
||||
candidate: henry
|
||||
question: fed8
|
||||
answer:
|
||||
other: Some generic user input
|
||||
options:
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 36.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 16.minutes %>
|
||||
|
||||
henry9:
|
||||
candidate: henry
|
||||
question: fed9
|
||||
answer:
|
||||
other: Brunch
|
||||
options:
|
||||
- Neither
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 38.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 18.minutes %>
|
||||
|
||||
henry10:
|
||||
candidate: henry
|
||||
question: fed10
|
||||
answer: ["Live long and prosper", "Who you calling Scruffy?"]
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
wade1:
|
||||
candidate: wade
|
||||
question: Cras justo odio, dapibus ac facilisis in, egestas eget quam. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit.
|
||||
answer: option 3
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 22.minutes %>
|
||||
|
||||
wade2:
|
||||
candidate: wade
|
||||
question: fed2
|
||||
answer: 'indexOf()'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 24.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 4.minutes %>
|
||||
|
||||
wade3:
|
||||
candidate: wade
|
||||
question: fed3
|
||||
answer: {html: '<h1>Salmon</h1>', css: 'h1 {color: salmon;}', js: '', text: 'Gotta lotta GOOD things on sale, strangah.'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 26.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 6.minutes %>
|
||||
|
||||
wade4:
|
||||
candidate: wade
|
||||
question: fed4
|
||||
answer: Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 28.minutes %>
|
||||
|
||||
wade5:
|
||||
candidate: wade
|
||||
question: fed5
|
||||
answer: 'Dynamic listeners'
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 30.minutes %>
|
||||
|
||||
wade6:
|
||||
candidate: wade
|
||||
question: fed6
|
||||
answer: Integer posuere erat a ante venenatis dapibus posuere velit aliquet.
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 32.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 12.minutes %>
|
||||
|
||||
wade7:
|
||||
candidate: wade
|
||||
question: fed7
|
||||
answer: {html: '<p>This means <strong>jQuery</strong> needs to be available in live-coder!</p>', css: "strong {font-size: 1.6em;}\n.green {color: green;}", js: '$("strong").addClass("green");'}
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 34.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 14.minutes %>
|
||||
|
||||
wade8:
|
||||
candidate: wade
|
||||
question: fed8
|
||||
answer:
|
||||
other: Some generic user input
|
||||
options:
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 36.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 16.minutes %>
|
||||
|
||||
wade9:
|
||||
candidate: wade
|
||||
question: fed9
|
||||
answer:
|
||||
other: Brunch
|
||||
options:
|
||||
- Neither
|
||||
- other
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 38.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 18.minutes %>
|
||||
|
||||
wade10:
|
||||
candidate: wade
|
||||
question: fed10
|
||||
answer: ["Live long and prosper", "Who you calling Scruffy?"]
|
||||
saved: 0
|
||||
submitted: true
|
||||
created_at: <%= DateTime.now() - 36.hours - 40.minutes %>
|
||||
updated_at: <%= DateTime.now() - 36.hours - 20.minutes %>
|
||||
|
||||
|
43
test/fixtures/candidates.yml
vendored
43
test/fixtures/candidates.yml
vendored
@ -59,6 +59,7 @@ richard: # Richard has completed AND submitted the test
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: 6NjnourLE6Y
|
||||
review_status: 1
|
||||
|
||||
juan: # Juan has chosen "finish later" for live coders
|
||||
name: Juan Campbell
|
||||
@ -69,3 +70,45 @@ juan: # Juan has chosen "finish later" for live coders
|
||||
completed: false
|
||||
reminded: true
|
||||
test_hash: qKQo0l4dyol
|
||||
|
||||
stacy: # Stacy has completed AND submitted the test
|
||||
name: Stacy Scott
|
||||
email: <%= CryptSerializer.dump 'stacy.scott@mailinator.com' %>
|
||||
experience: 7-9
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: s6oFExZliYYFx
|
||||
review_status: 2
|
||||
|
||||
henry: # Henry has completed AND submitted the test
|
||||
name: Henry Butler
|
||||
email: <%= CryptSerializer.dump 'henry.butler@mailinator.com' %>
|
||||
experience: 4-6
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: egPomAuVDeCEp
|
||||
|
||||
wade: # Wade has completed AND submitted the test
|
||||
name: Wade Armstrong
|
||||
email: <%= CryptSerializer.dump 'wade.armstrong@mailinator.com' %>
|
||||
experience: 0-3
|
||||
recruiter: recruiter
|
||||
quiz: fed
|
||||
completed: true
|
||||
reminded: false
|
||||
test_hash: BkSkpapJnkz2N
|
||||
|
||||
gustov: # Gustov is NOT for FED
|
||||
name: Gustov
|
||||
email: <%= CryptSerializer.dump 'gustov@mailinator.com' %>
|
||||
experience: 0-3
|
||||
recruiter: recruiter
|
||||
quiz: admin
|
||||
completed: false
|
||||
reminded: false
|
||||
test_hash: kp6tfghjyapJnkz2N
|
||||
|
||||
|
2
test/fixtures/questions.yml
vendored
2
test/fixtures/questions.yml
vendored
@ -55,7 +55,7 @@ fed5:
|
||||
fed6:
|
||||
quiz: fed
|
||||
question: Comment on how realistic the following image is.
|
||||
attachment: "http://dev.perficientxd.com/skill_assets/commets_css.jpg"
|
||||
attachment: "https://dev.perficientdigital.com/skills-app-images/commets_css.jpg"
|
||||
category: CSS
|
||||
input_type: text
|
||||
input_options:
|
||||
|
4
test/fixtures/reviewer_to_quizzes.yml
vendored
4
test/fixtures/reviewer_to_quizzes.yml
vendored
@ -7,3 +7,7 @@ one:
|
||||
two:
|
||||
user: reviewer2
|
||||
quiz: fed
|
||||
|
||||
three:
|
||||
user: manager
|
||||
quiz: fed
|
||||
|
68
test/fixtures/reviewer_votes.yml
vendored
Normal file
68
test/fixtures/reviewer_votes.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
gustov:
|
||||
candidate: gustov
|
||||
user_id: 12341234
|
||||
|
||||
|
||||
manager_richard:
|
||||
candidate: richard
|
||||
user: manager
|
||||
vote: 1
|
||||
|
||||
reviewer_richard:
|
||||
candidate: richard
|
||||
user: reviewer
|
||||
vote: 1
|
||||
|
||||
reviewer2_richard:
|
||||
candidate: richard
|
||||
user: reviewer2
|
||||
vote: 1
|
||||
|
||||
|
||||
|
||||
manager_stacy:
|
||||
candidate: stacy
|
||||
user: manager
|
||||
vote: 2
|
||||
|
||||
reviewer_stacy:
|
||||
candidate: stacy
|
||||
user: reviewer
|
||||
vote: 2
|
||||
|
||||
reviewer2_stacy:
|
||||
candidate: stacy
|
||||
user: reviewer2
|
||||
vote: 2
|
||||
|
||||
|
||||
manager_henry:
|
||||
candidate: henry
|
||||
user: manager
|
||||
vote: 0
|
||||
veto: 2
|
||||
|
||||
reviewer_henry:
|
||||
candidate: henry
|
||||
user: reviewer
|
||||
|
||||
reviewer2_henry:
|
||||
candidate: henry
|
||||
user: reviewer2
|
||||
|
||||
|
||||
manager_wade:
|
||||
candidate: wade
|
||||
user: manager
|
||||
vote: 2
|
||||
veto: 2
|
||||
|
||||
reviewer_wade:
|
||||
candidate: wade
|
||||
user: reviewer
|
||||
vote: 2
|
||||
|
||||
reviewer2_wade:
|
||||
candidate: wade
|
||||
user: reviewer2
|
||||
|
@ -24,4 +24,11 @@ class CandidateTest < ActiveSupport::TestCase
|
||||
|
||||
refute_equal email, enc_email
|
||||
end
|
||||
|
||||
test "can build reviewer records" do
|
||||
candidate = candidates(:dawn)
|
||||
|
||||
candidate.build_reviews
|
||||
assert_equal 3, candidate.votes.count
|
||||
end
|
||||
end
|
||||
|
34
test/models/reviewer_vote_test.rb
Normal file
34
test/models/reviewer_vote_test.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class ReviewerVoteTest < ActiveSupport::TestCase
|
||||
test "the truth" do
|
||||
assert ReviewerVoteTest
|
||||
end
|
||||
|
||||
test "richard has 3 votes" do
|
||||
richard = candidates(:richard)
|
||||
|
||||
assert_equal 3, richard.votes.size
|
||||
end
|
||||
|
||||
test "manager has 4 votes" do
|
||||
manager = users(:manager)
|
||||
|
||||
assert_equal 4, manager.votes.size
|
||||
end
|
||||
|
||||
test "richard has been approved" do
|
||||
richard = candidates(:richard)
|
||||
|
||||
assert richard.approved?
|
||||
refute richard.declined?
|
||||
end
|
||||
|
||||
test "stacy has been declined" do
|
||||
stacy = candidates(:stacy)
|
||||
|
||||
assert stacy.declined?
|
||||
refute stacy.approved?
|
||||
end
|
||||
end
|
@ -20,14 +20,14 @@ class UserTest < ActiveSupport::TestCase
|
||||
refute user.reviewer?
|
||||
end
|
||||
|
||||
test 'manager should act as manager' do
|
||||
test 'manager should act as manager and reviewer' do
|
||||
user = users(:manager)
|
||||
|
||||
assert user.acts_as_manager?
|
||||
assert user.acts_as_reviewer?
|
||||
|
||||
refute user.acts_as_admin?
|
||||
refute user.acts_as_recruiter?
|
||||
refute user.acts_as_reviewer?
|
||||
end
|
||||
|
||||
test 'manager should only be manager' do
|
||||
|
66
test/policies/reviewer_vote_policy_test.rb
Normal file
66
test/policies/reviewer_vote_policy_test.rb
Normal file
@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test_helper'
|
||||
|
||||
class ReviewerVotePolicyTest < PolicyAssertions::Test
|
||||
test 'should require current_user' do
|
||||
assert_raise Pundit::NotAuthorizedError do
|
||||
ReviewerVotePolicy.new(nil, ReviewerVote.first).view?
|
||||
end
|
||||
end
|
||||
|
||||
test 'should allow admin to scope' do
|
||||
scope = ReviewerVotePolicy::Scope.new(users(:admin), ReviewerVote).resolve
|
||||
assert_equal ReviewerVote.count, scope.count
|
||||
end
|
||||
|
||||
test 'should allow manager to scope' do
|
||||
scope = ReviewerVotePolicy::Scope.new(users(:manager), ReviewerVote).resolve
|
||||
assert_equal ReviewerVote.count, scope.count
|
||||
end
|
||||
|
||||
test 'should allow reviewer to scope' do
|
||||
scope = ReviewerVotePolicy::Scope.new(users(:reviewer), ReviewerVote).resolve
|
||||
assert_equal users(:reviewer).votes.count, scope.count
|
||||
end
|
||||
|
||||
test 'should NOT allow recruiter to scope' do
|
||||
scope = ReviewerVotePolicy::Scope.new(users(:recruiter), ReviewerVote).resolve
|
||||
assert_equal 0, scope.count
|
||||
end
|
||||
|
||||
def test_up
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:gustov)
|
||||
refute_permit users(:manager), reviewer_votes(:gustov)
|
||||
end
|
||||
|
||||
def test_down
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:gustov)
|
||||
refute_permit users(:manager), reviewer_votes(:gustov)
|
||||
end
|
||||
|
||||
def approve
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
end
|
||||
|
||||
def decline
|
||||
assert_permit users(:manager), reviewer_votes(:manager_richard)
|
||||
assert_permit users(:admin), reviewer_votes(:manager_henry)
|
||||
|
||||
refute_permit users(:recruiter), reviewer_votes(:manager_henry)
|
||||
refute_permit users(:reviewer), reviewer_votes(:reviewer_richard)
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user