fix conflict

This commit is contained in:
Juanjo Bazán
2015-08-05 14:46:18 +02:00
28 changed files with 363 additions and 159 deletions

1
.coveralls.yml Normal file
View File

@@ -0,0 +1 @@
service_name: travis-ci

2
.gitignore vendored
View File

@@ -19,3 +19,5 @@
/spec/examples.txt
/config/database.yml
/config/secrets.yml
/coverage

View File

@@ -56,5 +56,6 @@ end
group :test do
gem 'database_cleaner'
gem 'poltergeist'
gem 'coveralls', require: false
end

View File

@@ -75,6 +75,12 @@ GEM
execjs
coffee-script-source (1.9.1.1)
columnize (0.9.0)
coveralls (0.8.2)
json (~> 1.8)
rest-client (>= 1.6.8, < 2)
simplecov (~> 0.10.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
devise (3.5.1)
@@ -85,6 +91,9 @@ GEM
thread_safe (~> 0.1)
warden (~> 1.2.3)
diff-lcs (1.2.5)
docile (1.1.5)
domain_name (0.5.24)
unf (>= 0.0.5, < 1.0.0)
erubis (2.7.0)
execjs (2.5.2)
factory_girl (4.5.0)
@@ -97,6 +106,8 @@ GEM
sass (>= 3.3.0, < 3.5)
globalid (0.3.5)
activesupport (>= 4.1.0)
http-cookie (1.0.2)
domain_name (~> 0.5)
i18n (0.7.0)
jbuilder (2.3.1)
activesupport (>= 3.0.0, < 5)
@@ -121,6 +132,7 @@ GEM
mini_portile (0.6.2)
minitest (5.7.0)
multi_json (1.11.2)
netrc (0.10.3)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
orm_adapter (0.5.0)
@@ -164,6 +176,10 @@ GEM
recaptcha (0.4.0)
responders (2.1.0)
railties (>= 4.2.0, < 5)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rspec-core (3.3.1)
rspec-support (~> 3.3.0)
rspec-expectations (3.3.0)
@@ -191,6 +207,11 @@ GEM
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
simplecov (0.10.0)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
spring (1.3.6)
sprockets (3.2.0)
rack (~> 1.0)
@@ -198,9 +219,12 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
term-ansicolor (1.3.2)
tins (~> 1.0)
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
tins (1.5.4)
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
@@ -208,6 +232,9 @@ GEM
uglifier (2.7.1)
execjs (>= 0.3.0)
json (>= 1.8.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
warden (1.2.3)
rack (>= 1.0)
web-console (2.2.1)
@@ -232,6 +259,7 @@ DEPENDENCIES
capybara
ckeditor
coffee-rails (~> 4.1.0)
coveralls
database_cleaner
devise
factory_girl_rails

View File

@@ -1,5 +1,10 @@
# Aplicación de Participación Ciudadana del Ayuntamiento de Madrid
[![Build Status](https://travis-ci.org/AyuntamientoMadrid/participacion.svg?branch=master)](https://travis-ci.org/AyuntamientoMadrid/participacion)
[![Code Climate](https://codeclimate.com/github/AyuntamientoMadrid/participacion/badges/gpa.svg)](https://codeclimate.com/github/AyuntamientoMadrid/participacion)
[![Dependency Status](https://gemnasium.com/AyuntamientoMadrid/participacion.svg)](https://gemnasium.com/AyuntamientoMadrid/participacion)
[![Coverage Status](https://coveralls.io/repos/AyuntamientoMadrid/participacion/badge.svg?branch=master&service=github)](https://coveralls.io/github/AyuntamientoMadrid/participacion?branch=master)
Este es el repositorio de código abierto de la Aplicación de Participación Ciudadana del Ayuntamiento de Madrid.
## Estado del proyecto
@@ -22,8 +27,8 @@ cd participacion
bundle install
cp config/database.yml.example config/database.yml
cp config/secrets.yml.example config/secrets.yml
bundle exec bin/rake db:create db:schema_load
RAILS_ENV=test bundle exec rake db:create db:schema_load
bundle exec bin/rake db:create db:schema:load
RAILS_ENV=test bundle exec rake db:create db:schema:load
```
Para ejecutar la aplicación en local:

View File

@@ -7,4 +7,9 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def verify_captcha?(resource)
return true unless recaptcha_keys?
verify_recaptcha(model: resource)
end
end

View File

@@ -1,6 +1,6 @@
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_debate, :set_parent
before_action :set_debate, :set_parent, only: :create
respond_to :html, :js
def create
@@ -10,6 +10,12 @@ class CommentsController < ApplicationController
respond_with @comment
end
def vote
@comment = Comment.find(params[:id])
@comment.vote_by(voter: current_user, vote: params[:value])
respond_with @comment
end
private
def comment_params
params.require(:comments).permit(:commentable_type, :commentable_id, :body)

View File

@@ -1,7 +1,7 @@
class DebatesController < ApplicationController
include RecaptchaHelper
before_action :set_debate, only: [:show, :edit, :update]
before_action :authenticate_user!, except: [:show, :index]
before_action :set_debate, only: [:show, :edit, :update, :vote]
before_action :authenticate_user!, except: [:index, :show]
before_action :validate_ownership, only: [:edit, :update]
def index
@@ -26,7 +26,7 @@ class DebatesController < ApplicationController
def create
@debate = Debate.new(debate_params)
@debate.author = current_user
if verify_captcha? and @debate.save
if verify_captcha?(@debate) and @debate.save
redirect_to @debate, notice: t('flash.actions.create.notice', resource_name: 'Debate')
else
render :new
@@ -38,6 +38,10 @@ class DebatesController < ApplicationController
respond_with @debate
end
def vote
@debate.vote_by(voter: current_user, vote: params[:value])
end
private
def set_debate
@@ -51,10 +55,4 @@ class DebatesController < ApplicationController
def validate_ownership
raise ActiveRecord::RecordNotFound unless @debate.editable_by?(current_user)
end
def verify_captcha?
return true unless recaptcha_keys?
verify_recaptcha(model: @debate)
end
end

View File

@@ -1,7 +1,8 @@
class RegistrationsController < Devise::RegistrationsController
include RecaptchaHelper
def create
if verify_recaptcha
if verify_captcha?(resource)
super
else
build_resource(sign_up_params)

View File

@@ -1,21 +0,0 @@
class VotesController < ApplicationController
before_action :set_debate
before_action :authenticate_user!
respond_to :html, :js
def create
register_vote
notice = @debate.vote_registered? ? I18n.t("votes.notice_thanks") : I18n.t("votes.notice_already_registered")
respond_with @debate
end
private
def set_debate
@debate = Debate.find(params[:debate_id])
end
def register_vote
@debate.vote_by voter: current_user, vote: params[:value]
end
end

View File

@@ -1,5 +1,6 @@
class Comment < ActiveRecord::Base
acts_as_nested_set scope: [:commentable_id, :commentable_type]
acts_as_votable
validates :body, presence: true
validates :user, presence: true

View File

@@ -35,6 +35,10 @@ class Debate < ActiveRecord::Base
editable? && author == user
end
def description
super.try :html_safe
end
protected
def sanitize_description

View File

@@ -8,6 +8,11 @@
<%= comment.user.name %>&nbsp;&bullet;&nbsp;<%= time_ago_in_words(comment.created_at) %>
</span>
<p><%= comment.body %></p>
<span id="<%= dom_id(comment) %>_votes">
<%= render 'comments/votes', comment: comment %>
</span>
<% if user_signed_in? %>
<p class="reply"><%= render 'comments/form', {parent: comment, toggeable: true} %></p>
<% end %>

View File

@@ -0,0 +1,11 @@
<span class="in_favor">
<%= link_to "up", vote_comment_path(comment, value: 'yes'),
method: "post", remote: true %>
<%= comment.get_likes.size %>
</span>
<span class="against">
<%= link_to "down", vote_comment_path(comment, value: 'no'),
method: "post", remote: true %>
<%= comment.get_dislikes.size %>
</span>

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@comment) %>_votes").html('<%= j render("comments/votes", comment: @comment) %>');

View File

@@ -10,29 +10,15 @@
<p class="debate-info">
<i class="icon-chat-bubble-two"></i>&nbsp;<%= pluralize(debate.comment_threads.count, t("debates.debate.comment"), t("debates.debate.comments")) %>
</p>
<p><%= sanitize(truncate(debate.description, length: 200).html_safe) %></p>
<%= debate.description %>
<%= render "shared/tags", debate: debate %>
</div>
</div>
<div class="small-12 medium-3 column">
<div class="text-center votes">
<%= link_to debate_votes_path(debate, value: 'yes'), class: "like inline-block", title: t('votes.agree'), method: "post" do %>
<i class="icon-like"></i>
<span><%= percentage('likes', debate) %></span>
<% end %>
<span class="divider"></span>
<div id="<%= dom_id(debate) %>_votes" class="small-12 medium-3 column">
<%= render 'debates/votes_min', debate: debate %>
</div>
<%= link_to debate_votes_path(debate, value: 'no'), class: "unlike inline-block", title: t('votes.disagree'), method: "post" do %>
<i class="icon-unlike"></i>
<span><%= percentage('dislikes', debate) %></span>
<% end %>
<br>
<span class="total-votes">
<%= pluralize(debate.total_votes, t("debates.debate.vote"), t("debates.debate.votes")) %>
</span>
</div>
</div>
</div>
</div>
</div>

View File

@@ -16,22 +16,8 @@
</div>
<div class="row votes">
<div class="small-12 column">
<%= link_to debate_votes_path(featured_debate, value: "yes"), class: "like", title: t('votes.agree'), method: "post" do %>
<i class="icon-like"></i>
<span><%= percentage('likes', featured_debate) %></span>
<% end %>
<span class="divider"></span>
<%= link_to debate_votes_path(featured_debate, value: "no"), class: "unlike", title: t('votes.disagree'), method: "post" do %>
<i class="icon-unlike"></i>
<span><%= percentage('dislikes', featured_debate) %></span>
<% end %>
<span class="total-votes right">
<%= pluralize(featured_debate.total_votes, t("debates.debate.vote"), t("debates.debate.votes")) %>
</span>
<div id="<%= dom_id(featured_debate) %>_votes" class="small-12 column">
<%= render 'debates/featured_debate_votes', debate: featured_debate %>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<span id="in_favor">
<%= link_to vote_debate_path(debate, value: 'yes', partial: 'featured_debate_votes'),
class: "like", title: t('votes.agree'), method: "post", remote: true do %>
<i class="icon-like"></i>
<span><%= percentage('likes', debate) %></span>
<% end %>
</span>
<span class="divider"></span>
<span id="against">
<%= link_to vote_debate_path(debate, value: 'no', partial: 'featured_debate_votes'),
class: "unlike", title: t('votes.disagree'), method: "post", remote: true do %>
<i class="icon-unlike"></i>
<span><%= percentage('dislikes', debate) %></span>
<% end %>
</span>
<span class="total-votes right">
<%= pluralize(debate.total_votes, t("debates.debate.vote"), t("debates.debate.votes")) %>
</span>

View File

@@ -7,7 +7,8 @@
<div class="text-center">
<div id="in_favor" class="inline-block">
<%= link_to debate_votes_path(@debate, value: 'yes'), class: "like", title: t('votes.agree'), method: "post", remote: true do %>
<%= link_to vote_debate_path(@debate, value: 'yes', partial: 'votes'),
class: "like", title: t('votes.agree'), method: "post", remote: true do %>
<i class="icon-like"></i>
<span><%= percentage('likes', @debate) %></span>
<% end %>
@@ -16,7 +17,8 @@
<span class="divider"></span>
<div id="against" class="inline-block">
<%= link_to debate_votes_path(@debate, value: 'no'), class: "unlike", title: t('votes.disagree'), method: "post", remote: true do %>
<%= link_to vote_debate_path(@debate, value: 'no', partial: 'votes'),
class: "unlike", title: t('votes.disagree'), method: "post", remote: true do %>
<i class="icon-unlike"></i>
<span><%= percentage('dislikes', @debate) %></span>
<% end %>

View File

@@ -0,0 +1,22 @@
<div class="text-center votes">
<span id="in_favor">
<%= link_to vote_debate_path(debate, value: 'yes', partial: 'votes_min'),
class: "like inline-block", title: t('votes.agree'), method: "post", remote: true do %>
<i class="icon-like"></i>
<span><%= percentage('likes', debate) %></span>
<% end %>
</span>
<span class="divider"></span>
<span id="against">
<%= link_to vote_debate_path(debate, value: 'no', partial: 'votes_min'),
class: "unlike inline-block", title: t('votes.disagree'), method: "post", remote: true do %>
<i class="icon-unlike"></i>
<span><%= percentage('dislikes', debate) %></span>
<% end %>
</span>
<br>
<span class="total-votes">
<%= pluralize(debate.total_votes, t("debates.debate.vote"), t("debates.debate.votes")) %>
</span>
</div>

View File

@@ -7,11 +7,11 @@
<span class="author"><%= @debate.author.name %></span><span class="bullet">&nbsp;&bullet;&nbsp;</span> <%= l @debate.created_at.to_date %> <span class="bullet">&nbsp;&bullet;&nbsp;</span><i class="icon-chat-bubble-two"></i>&nbsp;<%= pluralize(@debate.comment_threads.count, t("debates.show.comment"), t("debates.show.comments")) %>
</div>
<h1><%= @debate.title %></h1>
<p><%= @debate.description %></p>
<%= @debate.description %>
<p><%= render 'shared/tags', debate: @debate %></p>
</div>
<div id="votes" class="small-12 medium-3 column votes">
<%= render 'votes/votes' %>
<div id="<%= dom_id(@debate) %>_votes" class="votes small-12 medium-3 column">
<%= render 'debates/votes' %>
<div class="text-center">
<% if user_signed_in? %>
<%= link_to t("debates.show.leave_comment"), "#comments", class: "leave-comment" %>

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@debate) %>_votes").html('<%= j render("debates/#{params[:partial]}", debate: @debate) %>');

View File

@@ -1 +0,0 @@
$("#votes").html("<%= j render('votes') %>");

View File

@@ -6,9 +6,18 @@ Rails.application.routes.draw do
# You can have the root of your site routed with "root"
root 'debates#index'
resources :debates do
resources :votes, only: :create
resources :comments, only: :create
member do
post :vote
end
resources :comments, only: :create, shallow: true do
member do
post :vote
end
end
end
resource :account, controller: "account", only: [:show, :update]

View File

@@ -54,21 +54,22 @@ feature 'Debates' do
expect(page).to have_content I18n.l(Date.today)
end
scenario 'JS injection is sanitized' do
scenario 'JS injection is prevented but safe html is respected' do
author = create(:user)
login_as(author)
visit new_debate_path
fill_in 'debate_title', with: 'A test'
fill_in 'debate_description', with: 'This is <script>alert("an attack");</script>'
fill_in 'debate_description', with: '<p>This is <script>alert("an attack");</script></p>'
check 'debate_terms_of_service'
click_button 'Create Debate'
expect(page).to have_content 'Debate was successfully created.'
expect(page).to have_content 'A test'
expect(page).to have_content 'This is alert("an attack");'
expect(page.html).to include '<p>This is alert("an attack");</p>'
expect(page.html).to_not include '<script>alert("an attack");</script>'
expect(page.html).to_not include '&lt;p&gt;This is'
end
scenario 'tagging using dangerous strings' do

View File

@@ -2,6 +2,8 @@ require 'rails_helper'
feature 'Votes' do
feature 'Debates' do
background do
@manuela = create(:user)
@pablo = create(:user)
@@ -28,7 +30,7 @@ feature 'Votes' do
end
end
scenario 'Create', :js do
scenario 'Create from debate show', :js do
find('#in_favor a').click
within('#in_favor') do
@@ -42,6 +44,47 @@ feature 'Votes' do
expect(page).to have_content "1 vote"
end
scenario 'Create from debate featured', :js do
visit debates_path
within("#featured-debates") do
find('#in_favor a').click
within('#in_favor') do
expect(page).to have_content "100%"
end
within('#against') do
expect(page).to have_content "0%"
end
expect(page).to have_content "1 vote"
end
expect(URI.parse(current_url).path).to eq(debates_path)
end
scenario 'Create from debate index', :js do
3.times { create(:debate) }
visit debates_path
within("#debates") do
expect(page).to have_css(".debate", count: 1)
find('#in_favor a').click
within('#in_favor') do
expect(page).to have_content "100%"
end
within('#against') do
expect(page).to have_content "0%"
end
expect(page).to have_content "1 vote"
end
expect(URI.parse(current_url).path).to eq(debates_path)
end
scenario 'Update', :js do
find('#in_favor a').click
find('#against a').click
@@ -72,4 +115,81 @@ feature 'Votes' do
expect(page).to have_content "1 vote"
end
end
feature 'Comments' do
background do
@manuela = create(:user)
@pablo = create(:user)
@debate = create(:debate)
@comment = create(:comment, commentable: @debate)
login_as(@manuela)
visit debate_path(@debate)
end
scenario 'Show' do
vote = create(:vote, voter: @manuela, votable: @comment, vote_flag: true)
vote = create(:vote, voter: @pablo, votable: @comment, vote_flag: false)
visit debate_path(@debate)
within("#comment_#{@comment.id}_votes") do
within(".in_favor") do
expect(page).to have_content "1"
end
within(".against") do
expect(page).to have_content "1"
end
end
end
scenario 'Create', :js do
within("#comment_#{@comment.id}_votes") do
find(".in_favor a").click
within(".in_favor") do
expect(page).to have_content "1"
end
within(".against") do
expect(page).to have_content "0"
end
end
end
scenario 'Update', :js do
within("#comment_#{@comment.id}_votes") do
find('.in_favor a').click
find('.against a').click
within('.in_favor') do
expect(page).to have_content "0"
end
within('.against') do
expect(page).to have_content "1"
end
end
end
scenario 'Trying to vote multiple times', :js do
within("#comment_#{@comment.id}_votes") do
find('.in_favor a').click
find('.in_favor a').click
within('.in_favor') do
expect(page).to have_content "1"
end
within('.against') do
expect(page).to have_content "0"
end
end
end
end
end

View File

@@ -20,17 +20,24 @@ describe Debate do
expect(@debate).to_not be_valid
end
it "should not be valid without a description" do
describe "#description" do
it "should be mandatory" do
@debate.description = nil
expect(@debate).to_not be_valid
end
it "should sanitize the description" do
it "should be sanitized" do
@debate.description = "<script>alert('danger');</script>"
@debate.valid?
expect(@debate.description).to eq("alert('danger');")
end
it "should be html_safe" do
@debate.description = "<script>alert('danger');</script>"
expect(@debate.description).to be_html_safe
end
end
it "should sanitize the tag list" do
@debate.tag_list = "user_id=1"
@debate.valid?

View File

@@ -1,3 +1,5 @@
require 'coveralls'
Coveralls.wear!('rails')
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?