Commit feab9afd authored by Douwe Maan's avatar Douwe Maan

Merge branch '44184-issues_ical_feed' into 'master'

Export assigned issues in iCalendar feed

Closes #44184

See merge request gitlab-org/gitlab-ce!17783
parents 2fdd8982 20dfe25c
...@@ -144,6 +144,9 @@ gem 'truncato', '~> 0.7.9' ...@@ -144,6 +144,9 @@ gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0' gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2' gem 'nokogiri', '~> 1.8.2'
# Calendar rendering
gem 'icalendar'
# Diffs # Diffs
gem 'diffy', '~> 3.1.0' gem 'diffy', '~> 3.1.0'
......
...@@ -410,6 +410,7 @@ GEM ...@@ -410,6 +410,7 @@ GEM
httpclient (2.8.3) httpclient (2.8.3)
i18n (0.9.5) i18n (0.9.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
icalendar (2.4.1)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
cause cause
...@@ -1060,6 +1061,7 @@ DEPENDENCIES ...@@ -1060,6 +1061,7 @@ DEPENDENCIES
html-pipeline (~> 2.7.1) html-pipeline (~> 2.7.1)
html2text html2text
httparty (~> 0.13.3) httparty (~> 0.13.3)
icalendar
influxdb (~> 0.2) influxdb (~> 0.2)
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
......
...@@ -17,10 +17,23 @@ module IssuesAction ...@@ -17,10 +17,23 @@ module IssuesAction
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def issues_calendar
@issues = issuables_collection
.non_archived
.with_due_date
.limit(100)
respond_to do |format|
format.ics { response.headers['Content-Disposition'] = 'inline' }
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private private
def finder_type def finder_type
(super if defined?(super)) || (super if defined?(super)) ||
(IssuesFinder if action_name == 'issues') (IssuesFinder if %w(issues issues_calendar).include?(action_name))
end end
end end
...@@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController ...@@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_personal_access_tokens_path redirect_to profile_personal_access_tokens_path
end end
def reset_rss_token def reset_feed_token
Users::UpdateService.new(current_user, user: @user).execute! do |user| Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_rss_token! user.reset_feed_token!
end end
flash[:notice] = "RSS token was successfully reset" flash[:notice] = 'Feed token was successfully reset'
redirect_to profile_personal_access_tokens_path redirect_to profile_personal_access_tokens_path
end end
......
...@@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update] before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available! before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update] before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index, :calendar]
# Allow write(create) issue # Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create] before_action :authorize_create_issue!, only: [:new, :create]
...@@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
def calendar
@issues = @issuables
.non_archived
.with_due_date
.limit(100)
respond_to do |format|
format.ics { response.headers['Content-Disposition'] = 'inline' }
end
end
def new def new
params[:issue] ||= ActionController::Parameters.new( params[:issue] ||= ActionController::Parameters.new(
assignee_ids: "" assignee_ids: ""
......
...@@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder ...@@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder
items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week) items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
elsif filter_by_due_this_month? elsif filter_by_due_this_month?
items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month) items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
elsif filter_by_due_next_month_and_previous_two_weeks?
items = items.due_between(Date.today - 2.weeks, (Date.today + 1.month).end_of_month)
end end
end end
...@@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder ...@@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder
due_date? && params[:due_date] == Issue::DueThisMonth.name due_date? && params[:due_date] == Issue::DueThisMonth.name
end end
def filter_by_due_next_month_and_previous_two_weeks?
due_date? && params[:due_date] == Issue::DueNextMonthAndPreviousTwoWeeks.name
end
def due_date? def due_date?
params[:due_date].present? params[:due_date].present?
end end
......
module CalendarHelper
def calendar_url_options
{ format: :ics,
feed_token: current_user.try(:feed_token),
due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name,
sort: 'closest_future_date' }
end
end
module RssHelper module RssHelper
def rss_url_options def rss_url_options
{ format: :atom, rss_token: current_user.try(:rss_token) } { format: :atom, feed_token: current_user.try(:feed_token) }
end end
end end
...@@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base ...@@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base
ignore_column :assignee_id, :branch_name, :deleted_at ignore_column :assignee_id, :branch_name, :deleted_at
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
Overdue = DueDateStruct.new('Overdue', 'overdue').freeze Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
belongs_to :project belongs_to :project
belongs_to :moved_to, class_name: 'Issue' belongs_to :moved_to, class_name: 'Issue'
...@@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base ...@@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base
scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') } scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)} scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
scope :with_due_date, -> { where('due_date IS NOT NULL') }
scope :without_due_date, -> { where(due_date: nil) } scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) } scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) } scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
...@@ -53,6 +55,7 @@ class Issue < ActiveRecord::Base ...@@ -53,6 +55,7 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :order_closest_future_date, -> { reorder('CASE WHEN due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - due_date) ASC') }
scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
...@@ -119,6 +122,7 @@ class Issue < ActiveRecord::Base ...@@ -119,6 +122,7 @@ class Issue < ActiveRecord::Base
def self.sort_by_attribute(method, excluded_labels: []) def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s case method.to_s
when 'closest_future_date' then order_closest_future_date
when 'due_date' then order_due_date_asc when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc when 'due_date_desc' then order_due_date_desc
......
...@@ -26,7 +26,7 @@ class User < ActiveRecord::Base ...@@ -26,7 +26,7 @@ class User < ActiveRecord::Base
ignore_column :authentication_token ignore_column :authentication_token
add_authentication_token_field :incoming_email_token add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token add_authentication_token_field :feed_token
default_value_for :admin, false default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external } default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
...@@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base ...@@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base
save save
end end
# each existing user needs to have an `rss_token`. # each existing user needs to have an `feed_token`.
# we do this on read since migrating all existing users is not a feasible # we do this on read since migrating all existing users is not a feasible
# solution. # solution.
def rss_token def feed_token
ensure_rss_token! ensure_feed_token!
end end
def sync_attribute?(attribute) def sync_attribute?(attribute)
......
...@@ -7,8 +7,7 @@ ...@@ -7,8 +7,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls .nav-controls
= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do = render 'shared/issuable/feed_buttons'
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
......
= render 'issues/issues_calendar', issues: @issues
...@@ -8,10 +8,7 @@ ...@@ -8,10 +8,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
= link_to safe_params.merge(rss_url_options), class: 'btn' do = render 'shared/issuable/feed_buttons'
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
......
= render 'issues/issues_calendar', issues: @issues
cal = Icalendar::Calendar.new
cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues'
@issues.includes(project: :namespace).each do |issue|
cal.event do |event|
event.dtstart = Icalendar::Values::Date.new(issue.due_date)
event.summary = "#{issue.title} (in #{issue.project.full_path})"
event.description = "Find out more at #{issue_url(issue)}"
event.url = issue_url(issue)
event.transp = 'TRANSPARENT'
end
end
cal.to_ical
...@@ -34,18 +34,18 @@ ...@@ -34,18 +34,18 @@
.row.prepend-top-default .row.prepend-top-default
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
RSS token Feed token
%p %p
Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs. Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when when your calendar application loads a personalized calendar, and is included in those feed URLs.
%p %p
It cannot be used to access any other data. It cannot be used to access any other data.
.col-lg-8.rss-token-reset .col-lg-8.feed-token-reset
= label_tag :rss_token, 'RSS token', class: "label-light" = label_tag :feed_token, 'Feed token', class: "label-light"
= text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()' = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
%p.form-text.text-muted %p.form-text.text-muted
Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you. Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you.
You should You should
= link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' } = link_to 'reset it', [:reset, :feed_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS or calendar URLs currently in use will stop working.' }
if that ever happens. if that ever happens.
- if incoming_email_token_enabled? - if incoming_email_token_enabled?
......
= link_to safe_params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do = render 'shared/issuable/feed_buttons'
= icon('rss')
- if @can_bulk_update - if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle" = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
- if show_new_issue_link?(@project) - if show_new_issue_link?(@project)
......
= render 'issues/issues_calendar', issues: @issues
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M15 5v7a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V5a2 2 0 0 1 2-2h1V2a1 1 0 1 1 2 0v1h4V2a1 1 0 1 1 2 0v1h1a2 2 0 0 1 2 2zM3 6v6a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6H3zm2 2h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z" fill="#000" fill-rule="evenodd"/></svg>
\ No newline at end of file
= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do
= icon('rss')
= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do
= custom_icon('icon_calendar')
---
title: Export assigned issues in iCalendar feed
merge_request: 17783
author: Imre Farkas
type: added
resource :dashboard, controller: 'dashboard', only: [] do resource :dashboard, controller: 'dashboard', only: [] do
get :issues, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues get :issues
get :merge_requests get :merge_requests
get :activity get :activity
......
...@@ -5,9 +5,10 @@ end ...@@ -5,9 +5,10 @@ end
constraints(::Constraints::GroupUrlConstrainer.new) do constraints(::Constraints::GroupUrlConstrainer.new) do
scope(path: 'groups/*id', scope(path: 'groups/*id',
controller: :groups, controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom|ics)/ }) do
scope(path: '-') do scope(path: '-') do
get :edit, as: :edit_group get :edit, as: :edit_group
get :issues, as: :issues_group_calendar, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues, as: :issues_group get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group get :projects, as: :projects_group
......
...@@ -7,7 +7,7 @@ resource :profile, only: [:show, :update] do ...@@ -7,7 +7,7 @@ resource :profile, only: [:show, :update] do
get :applications, to: 'oauth/applications#index' get :applications, to: 'oauth/applications#index'
put :reset_incoming_email_token put :reset_incoming_email_token
put :reset_rss_token put :reset_feed_token
put :update_username put :update_username
end end
......
...@@ -353,6 +353,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -353,6 +353,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do member do
post :toggle_subscription post :toggle_subscription
......
class RenameUsersRssTokenToFeedToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :users, :rss_token, :feed_token
end
def down
cleanup_concurrent_column_rename :users, :feed_token, :rss_token
end
end
class CleanupUsersRssTokenRename < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :users, :rss_token, :feed_token
end
def down
rename_column_concurrently :users, :feed_token, :rss_token
end
end
...@@ -2082,9 +2082,9 @@ ActiveRecord::Schema.define(version: 20180529093006) do ...@@ -2082,9 +2082,9 @@ ActiveRecord::Schema.define(version: 20180529093006) do
t.date "last_activity_on" t.date "last_activity_on"
t.boolean "notified_of_own_activity" t.boolean "notified_of_own_activity"
t.string "preferred_language" t.string "preferred_language"
t.string "rss_token"
t.integer "theme_id", limit: 2 t.integer "theme_id", limit: 2
t.integer "accepted_term_id" t.integer "accepted_term_id"
t.string "feed_token"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
...@@ -2092,12 +2092,12 @@ ActiveRecord::Schema.define(version: 20180529093006) do ...@@ -2092,12 +2092,12 @@ ActiveRecord::Schema.define(version: 20180529093006) do
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
add_index "users", ["feed_token"], name: "index_users_on_feed_token", using: :btree
add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree
add_index "users", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree add_index "users", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["rss_token"], name: "index_users_on_rss_token", using: :btree
add_index "users", ["state"], name: "index_users_on_state", using: :btree add_index "users", ["state"], name: "index_users_on_state", using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
......
...@@ -39,5 +39,13 @@ The day before an open issue is due, an email will be sent to all participants ...@@ -39,5 +39,13 @@ The day before an open issue is due, an email will be sent to all participants
of the issue. Both the due date and the day before are calculated using the of the issue. Both the due date and the day before are calculated using the
server's timezone. server's timezone.
Issues with due dates can also be exported as an iCalendar feed. The URL of the
feed can be added to calendar applications. The feed is accessible by clicking
on the _Subscribe to calendar_ button on the following pages:
- on the **Assigned Issues** page that is linked on the right-hand side of the
GitLab header
- on the **Project Issues** page
- on the **Group Issues** page
[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614 [ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
[permissions]: ../../permissions.md#project [permissions]: ../../permissions.md#project
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
end end
def find_sessionless_user def find_sessionless_user
find_user_from_access_token || find_user_from_rss_token find_user_from_access_token || find_user_from_feed_token
rescue Gitlab::Auth::AuthenticationError rescue Gitlab::Auth::AuthenticationError
nil nil
end end
......
...@@ -25,13 +25,15 @@ module Gitlab ...@@ -25,13 +25,15 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request? current_request.env['warden']&.authenticate if verified_request?
end end
def find_user_from_rss_token def find_user_from_feed_token
return unless current_request.path.ends_with?('.atom') || current_request.format.atom? return unless rss_request? || ics_request?
token = current_request.params[:rss_token].presence # NOTE: feed_token was renamed from rss_token but both needs to be supported because
# users might have already added the feed to their RSS reader before the rename
token = current_request.params[:feed_token].presence || current_request.params[:rss_token].presence
return unless token return unless token
User.find_by_rss_token(token) || raise(UnauthorizedError) User.find_by_feed_token(token) || raise(UnauthorizedError)
end end
def find_user_from_access_token def find_user_from_access_token
...@@ -104,6 +106,14 @@ module Gitlab ...@@ -104,6 +106,14 @@ module Gitlab
def current_request def current_request
@current_request ||= ensure_action_dispatch_request(request) @current_request ||= ensure_action_dispatch_request(request)
end end
def rss_request?
current_request.path.ends_with?('.atom') || current_request.format.atom?
end
def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics?
end
end end
end end
end end
...@@ -31,27 +31,27 @@ map $http_upgrade $connection_upgrade_gitlab { ...@@ -31,27 +31,27 @@ map $http_upgrade $connection_upgrade_gitlab {
log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent"; log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI ## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&... # In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&... # Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_temp_request_uri_1 { map $request_uri $gitlab_temp_request_uri_1 {
default $request_uri; default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## Remove authenticity_token from the request URI ## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&... # In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&... # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 { map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
default $gitlab_temp_request_uri_1; default $gitlab_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## Remove rss_token from the request URI ## Remove feed_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&... # In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&... # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri { map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
default $gitlab_temp_request_uri_2; default $gitlab_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## A version of the referer without the query string ## A version of the referer without the query string
......
...@@ -36,27 +36,27 @@ map $http_upgrade $connection_upgrade_gitlab_ssl { ...@@ -36,27 +36,27 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent"; log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI ## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&... # In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&... # Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_ssl_temp_request_uri_1 { map $request_uri $gitlab_ssl_temp_request_uri_1 {
default $request_uri; default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## Remove authenticity_token from the request URI ## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&... # In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&... # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 { map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
default $gitlab_ssl_temp_request_uri_1; default $gitlab_ssl_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## Remove rss_token from the request URI ## Remove feed_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&... # In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&... # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri { map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
default $gitlab_ssl_temp_request_uri_2; default $gitlab_ssl_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest"; ~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
} }
## A version of the referer without the query string ## A version of the referer without the query string
......
...@@ -6,9 +6,9 @@ namespace :tokens do ...@@ -6,9 +6,9 @@ namespace :tokens do
reset_all_users_token(:reset_incoming_email_token!) reset_all_users_token(:reset_incoming_email_token!)
end end
desc "Reset all GitLab RSS tokens" desc "Reset all GitLab feed tokens"
task reset_all_rss: :environment do task reset_all_feed: :environment do
reset_all_users_token(:reset_rss_token!) reset_all_users_token(:reset_feed_token!)
end end
def reset_all_users_token(reset_token_method) def reset_all_users_token(reset_token_method)
...@@ -31,8 +31,8 @@ class TmpUser < ActiveRecord::Base ...@@ -31,8 +31,8 @@ class TmpUser < ActiveRecord::Base
save!(validate: false) save!(validate: false)
end end
def reset_rss_token! def reset_feed_token!
write_new_token(:rss_token) write_new_token(:feed_token)
save!(validate: false) save!(validate: false)
end end
end end
...@@ -146,35 +146,43 @@ describe ApplicationController do ...@@ -146,35 +146,43 @@ describe ApplicationController do
end end
end end
describe '#authenticate_user_from_rss_token' do describe '#authenticate_sessionless_user!' do
describe "authenticating a user from an RSS token" do describe 'authenticating a user from a feed token' do
controller(described_class) do controller(described_class) do
def index def index
render text: 'authenticated' render text: 'authenticated'
end end
end end
context "when the 'rss_token' param is populated with the RSS token" do context "when the 'feed_token' param is populated with the feed token" do
context 'when the request format is atom' do context 'when the request format is atom' do
it "logs the user in" do it "logs the user in" do
get :index, rss_token: user.rss_token, format: :atom get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status 200 expect(response).to have_gitlab_http_status 200
expect(response.body).to eq 'authenticated' expect(response.body).to eq 'authenticated'
end end
end end
context 'when the request format is not atom' do context 'when the request format is ics' do
it "logs the user in" do
get :index, feed_token: user.feed_token, format: :ics
expect(response).to have_gitlab_http_status 200
expect(response.body).to eq 'authenticated'
end
end
context 'when the request format is neither atom nor ics' do
it "doesn't log the user in" do it "doesn't log the user in" do
get :index, rss_token: user.rss_token get :index, feed_token: user.feed_token
expect(response.status).not_to have_gitlab_http_status 200 expect(response.status).not_to have_gitlab_http_status 200
expect(response.body).not_to eq 'authenticated' expect(response.body).not_to eq 'authenticated'
end end
end end
end end
context "when the 'rss_token' param is populated with an invalid RSS token" do context "when the 'feed_token' param is populated with an invalid feed token" do
it "doesn't log the user" do it "doesn't log the user" do
get :index, rss_token: "token" get :index, feed_token: 'token', format: :atom
expect(response.status).not_to eq 200 expect(response.status).not_to eq 200
expect(response.body).not_to eq 'authenticated' expect(response.body).not_to eq 'authenticated'
end end
...@@ -454,7 +462,7 @@ describe ApplicationController do ...@@ -454,7 +462,7 @@ describe ApplicationController do
end end
it 'renders a 403 when the sessionless user did not accept the terms' do it 'renders a 403 when the sessionless user did not accept the terms' do
get :index, rss_token: user.rss_token, format: :atom get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(403) expect(response).to have_gitlab_http_status(403)
end end
...@@ -462,7 +470,7 @@ describe ApplicationController do ...@@ -462,7 +470,7 @@ describe ApplicationController do
it 'renders a 200 when the sessionless user accepted the terms' do it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user) accept_terms(user)
get :index, rss_token: user.rss_token, format: :atom get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
......
...@@ -12,10 +12,6 @@ FactoryBot.define do ...@@ -12,10 +12,6 @@ FactoryBot.define do
user.notification_email = user.email user.notification_email = user.email
end end
before(:create) do |user|
user.ensure_rss_token
end
trait :admin do trait :admin do
admin true admin true
end end
......
...@@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do ...@@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do
expect(body).to have_selector('title', text: "#{user.name} issues") expect(body).to have_selector('title', text: "#{user.name} issues")
end end
it "renders atom feed via RSS token" do it "renders atom feed via feed token" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id) visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: user.id)
expect(response_headers['Content-Type']).to have_content('application/atom+xml') expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues") expect(body).to have_selector('title', text: "#{user.name} issues")
end end
it "renders atom feed with url parameters" do it "renders atom feed with url parameters" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
...@@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do ...@@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do it "renders issue fields" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
...@@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do ...@@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do
end end
it "renders issue label and milestone info" do it "renders issue label and milestone info" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id) visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
......
...@@ -13,9 +13,9 @@ describe "Dashboard Feed" do ...@@ -13,9 +13,9 @@ describe "Dashboard Feed" do
end end
end end
context "projects atom feed via RSS token" do context "projects atom feed via feed token" do
it "renders projects atom feed" do it "renders projects atom feed" do
visit dashboard_projects_path(:atom, rss_token: user.rss_token) visit dashboard_projects_path(:atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title') expect(body).to have_selector('feed title')
end end
end end
...@@ -29,7 +29,7 @@ describe "Dashboard Feed" do ...@@ -29,7 +29,7 @@ describe "Dashboard Feed" do
project.add_master(user) project.add_master(user)
issue_event(issue, user) issue_event(issue, user)
note_event(note, user) note_event(note, user)
visit dashboard_projects_path(:atom, rss_token: user.rss_token) visit dashboard_projects_path(:atom, feed_token: user.feed_token)
end end
it "has issue opened event" do it "has issue opened event" do
......
...@@ -45,10 +45,10 @@ describe 'Issues Feed' do ...@@ -45,10 +45,10 @@ describe 'Issues Feed' do
end end
end end
context 'when authenticated via RSS token' do context 'when authenticated via feed token' do
it 'renders atom feed' do it 'renders atom feed' do
visit project_issues_path(project, :atom, visit project_issues_path(project, :atom,
rss_token: user.rss_token) feed_token: user.feed_token)
expect(response_headers['Content-Type']) expect(response_headers['Content-Type'])
.to have_content('application/atom+xml') .to have_content('application/atom+xml')
...@@ -61,24 +61,23 @@ describe 'Issues Feed' do ...@@ -61,24 +61,23 @@ describe 'Issues Feed' do
end end
it "renders atom feed with url parameters for project issues" do it "renders atom feed with url parameters for project issues" do
visit project_issues_path(project, visit project_issues_path(project, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
it "renders atom feed with url parameters for group issues" do it "renders atom feed with url parameters for group issues" do
visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id) visit issues_group_path(group, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
......
...@@ -13,9 +13,9 @@ describe "User Feed" do ...@@ -13,9 +13,9 @@ describe "User Feed" do
end end
end end
context 'user atom feed via RSS token' do context 'user atom feed via feed token' do
it "renders user atom feed" do it "renders user atom feed" do
visit user_path(user, :atom, rss_token: user.rss_token) visit user_path(user, :atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title') expect(body).to have_selector('feed title')
end end
end end
...@@ -51,7 +51,7 @@ describe "User Feed" do ...@@ -51,7 +51,7 @@ describe "User Feed" do
issue_event(issue, user) issue_event(issue, user)
note_event(note, user) note_event(note, user)
merge_request_event(merge_request, user) merge_request_event(merge_request, user)
visit user_path(user, :atom, rss_token: user.rss_token) visit user_path(user, :atom, feed_token: user.feed_token)
end end
it 'has issue opened event' do it 'has issue opened event' do
......
...@@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do ...@@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do
visit activity_dashboard_path visit activity_dashboard_path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'event filters', :js do context 'event filters', :js do
......
...@@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do ...@@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do
it 'updates atom feed link' do it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_id: user.id) visit_issues(milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a[title="Subscribe"]') link = find('.nav-controls a[title="Subscribe to RSS feed"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token]) expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('milestone_title' => ['']) expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('rss_token' => [user.rss_token]) expect(auto_discovery_params).to include('feed_token' => [user.feed_token])
expect(auto_discovery_params).to include('milestone_title' => ['']) expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end end
......
...@@ -56,8 +56,8 @@ RSpec.describe 'Dashboard Issues' do ...@@ -56,8 +56,8 @@ RSpec.describe 'Dashboard Issues' do
expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true) expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true)
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
describe 'new issue dropdown' do describe 'new issue dropdown' do
......
...@@ -10,7 +10,7 @@ feature 'Dashboard Projects' do ...@@ -10,7 +10,7 @@ feature 'Dashboard Projects' do
sign_in(user) sign_in(user)
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" do
before do before do
visit dashboard_projects_path visit dashboard_projects_path
end end
......
...@@ -15,8 +15,8 @@ feature 'Group activity page' do ...@@ -15,8 +15,8 @@ feature 'Group activity page' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when project is in the group', :js do context 'when project is in the group', :js do
...@@ -39,7 +39,7 @@ feature 'Group activity page' do ...@@ -39,7 +39,7 @@ feature 'Group activity page' do
visit path visit path
end end
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
...@@ -16,17 +16,21 @@ feature 'Group issues page' do ...@@ -16,17 +16,21 @@ feature 'Group issues page' do
let(:access_level) { ProjectFeature::ENABLED } let(:access_level) { ProjectFeature::ENABLED }
context 'when signed in' do context 'when signed in' do
let(:user) { user_in_group } let(:user) do
user_in_group.ensure_feed_token
it_behaves_like "it has an RSS button with current_user's RSS token" user_in_group.save!
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" user_in_group
end
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
let(:user) { nil } let(:user) { nil }
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
......
...@@ -14,7 +14,7 @@ feature 'Group show page' do ...@@ -14,7 +14,7 @@ feature 'Group show page' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
context 'when group does not exist' do context 'when group does not exist' do
let(:path) { group_path('not-exist') } let(:path) { group_path('not-exist') }
...@@ -29,7 +29,7 @@ feature 'Group show page' do ...@@ -29,7 +29,7 @@ feature 'Group show page' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
context 'when group has a public project', :js do context 'when group has a public project', :js do
......
require 'spec_helper'
describe 'Dashboard Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:project) { create(:project) }
before do
project.add_master(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit issues_dashboard_path(:ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit issues_dashboard_path(:ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit issues_dashboard_path(:ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit issues_dashboard_path(:ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
require 'spec_helper'
describe 'Group Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
before do
project.add_developer(user)
group.add_developer(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit issues_group_path(group, :ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit issues_group_path(group, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit issues_group_path(group, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit issues_group_path(group, :ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
require 'spec_helper'
describe 'Project Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) }
before do
project.add_developer(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit project_issues_path(project, :ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit project_issues_path(project, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit project_issues_path(project, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit project_issues_path(project, :ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
...@@ -468,13 +468,13 @@ describe 'Filter issues', :js do ...@@ -468,13 +468,13 @@ describe 'Filter issues', :js do
it "for #{type}" do it "for #{type}" do
visit path visit path
link = find_link('Subscribe') link = find_link('Subscribe to RSS feed')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expected = { expected = {
'rss_token' => [user.rss_token], 'feed_token' => [user.feed_token],
'milestone_title' => [milestone.title], 'milestone_title' => [milestone.title],
'assignee_id' => [user.id.to_s] 'assignee_id' => [user.id.to_s]
} }
......
...@@ -340,6 +340,20 @@ describe 'Issues' do ...@@ -340,6 +340,20 @@ describe 'Issues' do
expect(page).to have_content('baz') expect(page).to have_content('baz')
end end
end end
it 'filters by due next month and previous two weeks' do
foo.update(due_date: Date.today - 4.weeks)
bar.update(due_date: (Date.today + 2.months).beginning_of_month)
baz.update(due_date: Date.yesterday)
visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name)
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
end
end
end end
describe 'sorting by milestone' do describe 'sorting by milestone' do
......
...@@ -56,21 +56,21 @@ describe 'Profile account page', :js do ...@@ -56,21 +56,21 @@ describe 'Profile account page', :js do
end end
end end
describe 'when I reset RSS token' do describe 'when I reset feed token' do
before do before do
visit profile_personal_access_tokens_path visit profile_personal_access_tokens_path
end end
it 'resets RSS token' do it 'resets feed token' do
within('.rss-token-reset') do within('.feed-token-reset') do
previous_token = find("#rss_token").value previous_token = find("#feed_token").value
accept_confirm { click_link('reset it') } accept_confirm { click_link('reset it') }
expect(find('#rss_token').value).not_to eq(previous_token) expect(find('#feed_token').value).not_to eq(previous_token)
end end
expect(page).to have_content 'RSS token was successfully reset' expect(page).to have_content 'Feed token was successfully reset'
end end
end end
......
...@@ -15,7 +15,7 @@ feature 'Project Activity RSS' do ...@@ -15,7 +15,7 @@ feature 'Project Activity RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -23,6 +23,6 @@ feature 'Project Activity RSS' do ...@@ -23,6 +23,6 @@ feature 'Project Activity RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
end end
end end
...@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do ...@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do ...@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
...@@ -17,8 +17,8 @@ feature 'Project Issues RSS' do ...@@ -17,8 +17,8 @@ feature 'Project Issues RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -26,7 +26,7 @@ feature 'Project Issues RSS' do ...@@ -26,7 +26,7 @@ feature 'Project Issues RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
...@@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do ...@@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do ...@@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
...@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do ...@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do ...@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without an RSS token" it_behaves_like "an autodiscoverable RSS feed without a feed token"
end end
end end
...@@ -10,7 +10,7 @@ feature 'User RSS' do ...@@ -10,7 +10,7 @@ feature 'User RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's RSS token" it_behaves_like "it has an RSS button with current_user's feed token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -18,6 +18,6 @@ feature 'User RSS' do ...@@ -18,6 +18,6 @@ feature 'User RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without an RSS token" it_behaves_like "it has an RSS button without a feed token"
end end
end end
require 'spec_helper'
describe CalendarHelper do
describe '#calendar_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
expect(helper.calendar_url_options).to include feed_token: current_user.feed_token
end
end
context 'when signed out' do
it "does not have a feed_token" do
allow(helper).to receive(:current_user).and_return(nil)
expect(helper.calendar_url_options[:feed_token]).to be_nil
end
end
end
end
...@@ -3,17 +3,17 @@ require 'spec_helper' ...@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do describe RssHelper do
describe '#rss_url_options' do describe '#rss_url_options' do
context 'when signed in' do context 'when signed in' do
it "includes the current_user's rss_token" do it "includes the current_user's feed_token" do
current_user = create(:user) current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
expect(helper.rss_url_options).to include rss_token: current_user.rss_token expect(helper.rss_url_options).to include feed_token: current_user.feed_token
end end
end end
context 'when signed out' do context 'when signed out' do
it "does not have an rss_token" do it "does not have a feed_token" do
allow(helper).to receive(:current_user).and_return(nil) allow(helper).to receive(:current_user).and_return(nil)
expect(helper.rss_url_options[:rss_token]).to be_nil expect(helper.rss_url_options[:feed_token]).to be_nil
end end
end end
end end
......
...@@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do ...@@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do
describe '#find_sessionless_user' do describe '#find_sessionless_user' do
let!(:access_token_user) { build(:user) } let!(:access_token_user) { build(:user) }
let!(:rss_token_user) { build(:user) } let!(:feed_token_user) { build(:user) }
it 'returns access_token user first' do it 'returns access_token user first' do
allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user)
allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user).to eq access_token_user expect(subject.find_sessionless_user).to eq access_token_user
end end
it 'returns rss_token user if no access_token user found' do it 'returns feed_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user).to eq rss_token_user expect(subject.find_sessionless_user).to eq feed_token_user
end end
it 'returns nil if no user found' do it 'returns nil if no user found' do
......
...@@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do
end end
end end
describe '#find_user_from_rss_token' do describe '#find_user_from_feed_token' do
context 'when the request format is atom' do context 'when the request format is atom' do
before do before do
env['HTTP_ACCEPT'] = 'application/atom+xml' env['HTTP_ACCEPT'] = 'application/atom+xml'
end end
it 'returns user if valid rss_token' do context 'when feed_token param is provided' do
set_param(:rss_token, user.rss_token) it 'returns user if valid feed_token' do
set_param(:feed_token, user.feed_token)
expect(find_user_from_rss_token).to eq user expect(find_user_from_feed_token).to eq user
end end
it 'returns nil if feed_token is blank' do
expect(find_user_from_feed_token).to be_nil
end
it 'returns exception if invalid feed_token' do
set_param(:feed_token, 'invalid_token')
it 'returns nil if rss_token is blank' do expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
expect(find_user_from_rss_token).to be_nil end
end end
it 'returns exception if invalid rss_token' do context 'when rss_token param is provided' do
set_param(:rss_token, 'invalid_token') it 'returns user if valid rssd_token' do
set_param(:rss_token, user.feed_token)
expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect(find_user_from_feed_token).to eq user
end
it 'returns nil if rss_token is blank' do
expect(find_user_from_feed_token).to be_nil
end
it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token')
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end end
end end
context 'when the request format is not atom' do context 'when the request format is not atom' do
it 'returns nil' do it 'returns nil' do
set_param(:rss_token, user.rss_token) set_param(:feed_token, user.feed_token)
expect(find_user_from_rss_token).to be_nil expect(find_user_from_feed_token).to be_nil
end end
end end
...@@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do
it 'the method call does not modify the original value' do it 'the method call does not modify the original value' do
env['action_dispatch.request.formats'] = nil env['action_dispatch.request.formats'] = nil
find_user_from_rss_token find_user_from_feed_token
expect(env['action_dispatch.request.formats']).to be_nil expect(env['action_dispatch.request.formats']).to be_nil
end end
......
...@@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do ...@@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do
end end
describe User, 'TokenAuthenticatable' do describe User, 'TokenAuthenticatable' do
let(:token_field) { :rss_token } let(:token_field) { :feed_token }
it_behaves_like 'TokenAuthenticatable' it_behaves_like 'TokenAuthenticatable'
describe 'ensures authentication token' do describe 'ensures authentication token' do
......
...@@ -644,13 +644,13 @@ describe User do ...@@ -644,13 +644,13 @@ describe User do
end end
end end
describe 'rss token' do describe 'feed token' do
it 'ensures an rss token on read' do it 'ensures a feed token on read' do
user = create(:user, rss_token: nil) user = create(:user, feed_token: nil)
rss_token = user.rss_token feed_token = user.feed_token
expect(rss_token).not_to be_blank expect(feed_token).not_to be_blank
expect(user.reload.rss_token).to eq rss_token expect(user.reload.feed_token).to eq feed_token
end end
end end
......
...@@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do ...@@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do
end end
def rss_url(user) def rss_url(user)
"/dashboard/projects.atom?rss_token=#{user.rss_token}" "/dashboard/projects.atom?feed_token=#{user.feed_token}"
end end
def private_token_headers(user) def private_token_headers(user)
......
...@@ -162,8 +162,8 @@ describe ProfilesController, "routing" do ...@@ -162,8 +162,8 @@ describe ProfilesController, "routing" do
expect(get("/profile/audit_log")).to route_to('profiles#audit_log') expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end end
it "to #reset_rss_token" do it "to #reset_feed_token" do
expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token') expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token')
end end
it "to #show" do it "to #show" do
...@@ -249,7 +249,11 @@ describe DashboardController, "routing" do ...@@ -249,7 +249,11 @@ describe DashboardController, "routing" do
end end
it "to #issues" do it "to #issues" do
expect(get("/dashboard/issues")).to route_to('dashboard#issues') expect(get("/dashboard/issues.html")).to route_to('dashboard#issues', format: 'html')
end
it "to #calendar_issues" do
expect(get("/dashboard/issues.ics")).to route_to('dashboard#issues_calendar', format: 'ics')
end end
it "to #merge_requests" do it "to #merge_requests" do
......
shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
it "has an RSS autodiscovery link tag with current_user's RSS token" do it "has an RSS autodiscovery link tag with current_user's feed token" do
expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false) expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
end end
end end
shared_examples "it has an RSS button with current_user's RSS token" do shared_examples "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's RSS token" do it "shows the RSS button with current_user's feed token" do
expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']") expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
end end
end end
shared_examples "an autodiscoverable RSS feed without an RSS token" do shared_examples "an autodiscoverable RSS feed without a feed token" do
it "has an RSS autodiscovery link tag without an RSS token" do it "has an RSS autodiscovery link tag without a feed token" do
expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false) expect(page).to have_css("link[type*='atom+xml']:not([href*='feed_token'])", visible: false)
end end
end end
shared_examples "it has an RSS button without an RSS token" do shared_examples "it has an RSS button without a feed token" do
it "shows the RSS button without an RSS token" do it "shows the RSS button without a feed token" do
expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])") expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
end end
end end
...@@ -13,9 +13,9 @@ describe 'tokens rake tasks' do ...@@ -13,9 +13,9 @@ describe 'tokens rake tasks' do
end end
end end
describe 'reset_all_rss task' do describe 'reset_all_feed task' do
it 'invokes create_hooks task' do it 'invokes create_hooks task' do
expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token } expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment