Export assigned issues in iCalendar feed
This commit is contained in:
parent
2fdd8982f8
commit
20dfe25c15
69 changed files with 568 additions and 185 deletions
3
Gemfile
3
Gemfile
|
@ -144,6 +144,9 @@ gem 'truncato', '~> 0.7.9'
|
|||
gem 'bootstrap_form', '~> 2.7.0'
|
||||
gem 'nokogiri', '~> 1.8.2'
|
||||
|
||||
# Calendar rendering
|
||||
gem 'icalendar'
|
||||
|
||||
# Diffs
|
||||
gem 'diffy', '~> 3.1.0'
|
||||
|
||||
|
|
|
@ -410,6 +410,7 @@ GEM
|
|||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
icalendar (2.4.1)
|
||||
ice_nine (0.11.2)
|
||||
influxdb (0.2.3)
|
||||
cause
|
||||
|
@ -1060,6 +1061,7 @@ DEPENDENCIES
|
|||
html-pipeline (~> 2.7.1)
|
||||
html2text
|
||||
httparty (~> 0.13.3)
|
||||
icalendar
|
||||
influxdb (~> 0.2)
|
||||
jira-ruby (~> 1.4)
|
||||
jquery-atwho-rails (~> 1.3.2)
|
||||
|
|
|
@ -17,10 +17,23 @@ module IssuesAction
|
|||
end
|
||||
# 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
|
||||
|
||||
def finder_type
|
||||
(super if defined?(super)) ||
|
||||
(IssuesFinder if action_name == 'issues')
|
||||
(IssuesFinder if %w(issues issues_calendar).include?(action_name))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController
|
|||
redirect_to profile_personal_access_tokens_path
|
||||
end
|
||||
|
||||
def reset_rss_token
|
||||
def reset_feed_token
|
||||
Users::UpdateService.new(current_user, user: @user).execute! do |user|
|
||||
user.reset_rss_token!
|
||||
user.reset_feed_token!
|
||||
end
|
||||
|
||||
flash[:notice] = "RSS token was successfully reset"
|
||||
flash[:notice] = 'Feed token was successfully reset'
|
||||
|
||||
redirect_to profile_personal_access_tokens_path
|
||||
end
|
||||
|
|
|
@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
|
||||
before_action :check_issues_available!
|
||||
before_action :issue, except: [:index, :new, :create, :bulk_update]
|
||||
before_action :set_issuables_index, only: [:index]
|
||||
before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
|
||||
before_action :set_issuables_index, only: [:index, :calendar]
|
||||
|
||||
# Allow write(create) issue
|
||||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
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
|
||||
params[:issue] ||= ActionController::Parameters.new(
|
||||
assignee_ids: ""
|
||||
|
|
|
@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder
|
|||
items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
|
||||
elsif filter_by_due_this_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
|
||||
|
||||
|
@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder
|
|||
due_date? && params[:due_date] == Issue::DueThisMonth.name
|
||||
end
|
||||
|
||||
def filter_by_due_next_month_and_previous_two_weeks?
|
||||
due_date? && params[:due_date] == Issue::DueNextMonthAndPreviousTwoWeeks.name
|
||||
end
|
||||
|
||||
def due_date?
|
||||
params[:due_date].present?
|
||||
end
|
||||
|
|
8
app/helpers/calendar_helper.rb
Normal file
8
app/helpers/calendar_helper.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
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
|
|
@ -1,5 +1,5 @@
|
|||
module RssHelper
|
||||
def rss_url_options
|
||||
{ format: :atom, rss_token: current_user.try(:rss_token) }
|
||||
{ format: :atom, feed_token: current_user.try(:feed_token) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
ignore_column :assignee_id, :branch_name, :deleted_at
|
||||
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
|
||||
Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
|
||||
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
|
||||
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
|
||||
Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
|
||||
DueThisWeek = DueDateStruct.new('Due This Week', 'week').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 :moved_to, class_name: 'Issue'
|
||||
|
@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base
|
|||
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 :with_due_date, -> { where('due_date IS NOT NULL') }
|
||||
scope :without_due_date, -> { where(due_date: nil) }
|
||||
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) }
|
||||
|
@ -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_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) }
|
||||
|
||||
|
@ -119,6 +122,7 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
def self.sort_by_attribute(method, excluded_labels: [])
|
||||
case method.to_s
|
||||
when 'closest_future_date' then order_closest_future_date
|
||||
when 'due_date' then order_due_date_asc
|
||||
when 'due_date_asc' then order_due_date_asc
|
||||
when 'due_date_desc' then order_due_date_desc
|
||||
|
|
|
@ -26,7 +26,7 @@ class User < ActiveRecord::Base
|
|||
ignore_column :authentication_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(:external) { Gitlab::CurrentSettings.user_default_external }
|
||||
|
@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base
|
|||
save
|
||||
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
|
||||
# solution.
|
||||
def rss_token
|
||||
ensure_rss_token!
|
||||
def feed_token
|
||||
ensure_feed_token!
|
||||
end
|
||||
|
||||
def sync_attribute?(attribute)
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
.top-area
|
||||
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
|
||||
.nav-controls
|
||||
= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
|
||||
= icon('rss')
|
||||
= render 'shared/issuable/feed_buttons'
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
|
||||
|
||||
= render 'shared/issuable/filter', type: :issues
|
||||
|
|
1
app/views/dashboard/issues_calendar.ics.haml
Normal file
1
app/views/dashboard/issues_calendar.ics.haml
Normal file
|
@ -0,0 +1 @@
|
|||
= render 'issues/issues_calendar', issues: @issues
|
|
@ -8,10 +8,7 @@
|
|||
.top-area
|
||||
= render 'shared/issuable/nav', type: :issues
|
||||
.nav-controls
|
||||
= link_to safe_params.merge(rss_url_options), class: 'btn' do
|
||||
= icon('rss')
|
||||
%span.icon-label
|
||||
Subscribe
|
||||
= render 'shared/issuable/feed_buttons'
|
||||
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
|
||||
|
||||
= render 'shared/issuable/search_bar', type: :issues
|
||||
|
|
1
app/views/groups/issues_calendar.ics.haml
Normal file
1
app/views/groups/issues_calendar.ics.haml
Normal file
|
@ -0,0 +1 @@
|
|||
= render 'issues/issues_calendar', issues: @issues
|
15
app/views/issues/_issues_calendar.ics.ruby
Normal file
15
app/views/issues/_issues_calendar.ics.ruby
Normal file
|
@ -0,0 +1,15 @@
|
|||
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 @@
|
|||
.row.prepend-top-default
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.prepend-top-0
|
||||
RSS token
|
||||
Feed token
|
||||
%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
|
||||
It cannot be used to access any other data.
|
||||
.col-lg-8.rss-token-reset
|
||||
= label_tag :rss_token, 'RSS token', class: "label-light"
|
||||
= text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
|
||||
.col-lg-8.feed-token-reset
|
||||
= label_tag :feed_token, 'Feed token', class: "label-light"
|
||||
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
|
||||
%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
|
||||
= 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 incoming_email_token_enabled?
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= link_to safe_params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
|
||||
= icon('rss')
|
||||
= render 'shared/issuable/feed_buttons'
|
||||
|
||||
- if @can_bulk_update
|
||||
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
|
||||
- if show_new_issue_link?(@project)
|
||||
|
|
1
app/views/projects/issues/calendar.ics.haml
Normal file
1
app/views/projects/issues/calendar.ics.haml
Normal file
|
@ -0,0 +1 @@
|
|||
= render 'issues/issues_calendar', issues: @issues
|
1
app/views/shared/icons/_icon_calendar.svg
Normal file
1
app/views/shared/icons/_icon_calendar.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<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>
|
After Width: | Height: | Size: 321 B |
4
app/views/shared/issuable/_feed_buttons.html.haml
Normal file
4
app/views/shared/issuable/_feed_buttons.html.haml
Normal file
|
@ -0,0 +1,4 @@
|
|||
= 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')
|
5
changelogs/unreleased/44184-issues_ical_feed.yml
Normal file
5
changelogs/unreleased/44184-issues_ical_feed.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Export assigned issues in iCalendar feed
|
||||
merge_request: 17783
|
||||
author: Imre Farkas
|
||||
type: added
|
|
@ -1,4 +1,5 @@
|
|||
resource :dashboard, controller: 'dashboard', only: [] do
|
||||
get :issues, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
|
||||
get :issues
|
||||
get :merge_requests
|
||||
get :activity
|
||||
|
|
|
@ -5,9 +5,10 @@ end
|
|||
constraints(::Constraints::GroupUrlConstrainer.new) do
|
||||
scope(path: 'groups/*id',
|
||||
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
|
||||
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 :merge_requests, as: :merge_requests_group
|
||||
get :projects, as: :projects_group
|
||||
|
|
|
@ -7,7 +7,7 @@ resource :profile, only: [:show, :update] do
|
|||
get :applications, to: 'oauth/applications#index'
|
||||
|
||||
put :reset_incoming_email_token
|
||||
put :reset_rss_token
|
||||
put :reset_feed_token
|
||||
put :update_username
|
||||
end
|
||||
|
||||
|
|
|
@ -353,6 +353,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
|
||||
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
|
||||
member do
|
||||
post :toggle_subscription
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
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
|
|
@ -0,0 +1,13 @@
|
|||
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
|
|||
t.date "last_activity_on"
|
||||
t.boolean "notified_of_own_activity"
|
||||
t.string "preferred_language"
|
||||
t.string "rss_token"
|
||||
t.integer "theme_id", limit: 2
|
||||
t.integer "accepted_term_id"
|
||||
t.string "feed_token"
|
||||
end
|
||||
|
||||
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
|
||||
|
@ -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", ["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", ["feed_token"], name: "index_users_on_feed_token", 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", ["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", ["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", ["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"}
|
||||
|
|
|
@ -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
|
||||
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
|
||||
[permissions]: ../../permissions.md#project
|
||||
|
|
|
@ -16,7 +16,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
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
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -25,13 +25,15 @@ module Gitlab
|
|||
current_request.env['warden']&.authenticate if verified_request?
|
||||
end
|
||||
|
||||
def find_user_from_rss_token
|
||||
return unless current_request.path.ends_with?('.atom') || current_request.format.atom?
|
||||
def find_user_from_feed_token
|
||||
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
|
||||
|
||||
User.find_by_rss_token(token) || raise(UnauthorizedError)
|
||||
User.find_by_feed_token(token) || raise(UnauthorizedError)
|
||||
end
|
||||
|
||||
def find_user_from_access_token
|
||||
|
@ -104,6 +106,14 @@ module Gitlab
|
|||
def current_request
|
||||
@current_request ||= ensure_action_dispatch_request(request)
|
||||
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
|
||||
|
|
|
@ -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";
|
||||
|
||||
## Remove private_token from the request URI
|
||||
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&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&feed_token=unfiltered&...
|
||||
map $request_uri $gitlab_temp_request_uri_1 {
|
||||
default $request_uri;
|
||||
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
|
||||
}
|
||||
|
||||
## Remove authenticity_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
|
||||
map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
|
||||
default $gitlab_temp_request_uri_1;
|
||||
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
|
||||
}
|
||||
|
||||
## Remove rss_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
|
||||
## Remove feed_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
|
||||
map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
|
||||
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
|
||||
|
|
|
@ -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";
|
||||
|
||||
## Remove private_token from the request URI
|
||||
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&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&feed_token=unfiltered&...
|
||||
map $request_uri $gitlab_ssl_temp_request_uri_1 {
|
||||
default $request_uri;
|
||||
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
|
||||
}
|
||||
|
||||
## Remove authenticity_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_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 {
|
||||
default $gitlab_ssl_temp_request_uri_1;
|
||||
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
|
||||
}
|
||||
|
||||
## Remove rss_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
|
||||
## Remove feed_token from the request URI
|
||||
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
|
||||
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
|
||||
map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
|
||||
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
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace :tokens do
|
|||
reset_all_users_token(:reset_incoming_email_token!)
|
||||
end
|
||||
|
||||
desc "Reset all GitLab RSS tokens"
|
||||
task reset_all_rss: :environment do
|
||||
reset_all_users_token(:reset_rss_token!)
|
||||
desc "Reset all GitLab feed tokens"
|
||||
task reset_all_feed: :environment do
|
||||
reset_all_users_token(:reset_feed_token!)
|
||||
end
|
||||
|
||||
def reset_all_users_token(reset_token_method)
|
||||
|
@ -31,8 +31,8 @@ class TmpUser < ActiveRecord::Base
|
|||
save!(validate: false)
|
||||
end
|
||||
|
||||
def reset_rss_token!
|
||||
write_new_token(:rss_token)
|
||||
def reset_feed_token!
|
||||
write_new_token(:feed_token)
|
||||
save!(validate: false)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -146,35 +146,43 @@ describe ApplicationController do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#authenticate_user_from_rss_token' do
|
||||
describe "authenticating a user from an RSS token" do
|
||||
describe '#authenticate_sessionless_user!' do
|
||||
describe 'authenticating a user from a feed token' do
|
||||
controller(described_class) do
|
||||
def index
|
||||
render text: 'authenticated'
|
||||
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
|
||||
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.body).to eq 'authenticated'
|
||||
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
|
||||
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.body).not_to eq 'authenticated'
|
||||
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
|
||||
get :index, rss_token: "token"
|
||||
get :index, feed_token: 'token', format: :atom
|
||||
expect(response.status).not_to eq 200
|
||||
expect(response.body).not_to eq 'authenticated'
|
||||
end
|
||||
|
@ -454,7 +462,7 @@ describe ApplicationController do
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
@ -462,7 +470,7 @@ describe ApplicationController do
|
|||
it 'renders a 200 when the sessionless user accepted the terms' do
|
||||
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)
|
||||
end
|
||||
|
|
|
@ -12,10 +12,6 @@ FactoryBot.define do
|
|||
user.notification_email = user.email
|
||||
end
|
||||
|
||||
before(:create) do |user|
|
||||
user.ensure_rss_token
|
||||
end
|
||||
|
||||
trait :admin do
|
||||
admin true
|
||||
end
|
||||
|
|
|
@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do
|
|||
expect(body).to have_selector('title', text: "#{user.name} issues")
|
||||
end
|
||||
|
||||
it "renders atom feed via RSS token" do
|
||||
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id)
|
||||
it "renders atom feed via feed token" do
|
||||
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(body).to have_selector('title', text: "#{user.name} issues")
|
||||
end
|
||||
|
||||
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"]')
|
||||
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('assignee_id' => [user.id.to_s])
|
||||
end
|
||||
|
@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do
|
|||
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
|
||||
|
||||
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}')]")
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do
|
|||
end
|
||||
|
||||
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}')]")
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@ describe "Dashboard Feed" do
|
|||
end
|
||||
end
|
||||
|
||||
context "projects atom feed via RSS token" do
|
||||
context "projects atom feed via feed token" 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')
|
||||
end
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ describe "Dashboard Feed" do
|
|||
project.add_master(user)
|
||||
issue_event(issue, 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
|
||||
|
||||
it "has issue opened event" do
|
||||
|
|
|
@ -45,10 +45,10 @@ describe 'Issues Feed' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when authenticated via RSS token' do
|
||||
context 'when authenticated via feed token' do
|
||||
it 'renders atom feed' do
|
||||
visit project_issues_path(project, :atom,
|
||||
rss_token: user.rss_token)
|
||||
feed_token: user.feed_token)
|
||||
|
||||
expect(response_headers['Content-Type'])
|
||||
.to have_content('application/atom+xml')
|
||||
|
@ -61,24 +61,23 @@ describe 'Issues Feed' do
|
|||
end
|
||||
|
||||
it "renders atom feed with url parameters for project issues" do
|
||||
visit project_issues_path(project,
|
||||
:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
|
||||
visit project_issues_path(project, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
|
||||
|
||||
link = find('link[type="application/atom+xml"]')
|
||||
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('assignee_id' => [user.id.to_s])
|
||||
end
|
||||
|
||||
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"]')
|
||||
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('assignee_id' => [user.id.to_s])
|
||||
end
|
||||
|
|
|
@ -13,9 +13,9 @@ describe "User Feed" do
|
|||
end
|
||||
end
|
||||
|
||||
context 'user atom feed via RSS token' do
|
||||
context 'user atom feed via feed token' 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')
|
||||
end
|
||||
end
|
||||
|
@ -51,7 +51,7 @@ describe "User Feed" do
|
|||
issue_event(issue, user)
|
||||
note_event(note, 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
|
||||
|
||||
it 'has issue opened event' do
|
||||
|
|
|
@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do
|
|||
visit activity_dashboard_path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
context 'event filters', :js do
|
||||
|
|
|
@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do
|
|||
it 'updates atom feed link' do
|
||||
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)
|
||||
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
|
||||
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('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('assignee_id' => [user.id.to_s])
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
describe 'new issue dropdown' do
|
||||
|
|
|
@ -10,7 +10,7 @@ feature 'Dashboard Projects' do
|
|||
sign_in(user)
|
||||
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
|
||||
visit dashboard_projects_path
|
||||
end
|
||||
|
|
|
@ -15,8 +15,8 @@ feature 'Group activity page' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
context 'when project is in the group', :js do
|
||||
|
@ -39,7 +39,7 @@ feature 'Group activity page' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button without an RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
|
||||
it_behaves_like "it has an RSS button without a feed token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,17 +16,21 @@ feature 'Group issues page' do
|
|||
let(:access_level) { ProjectFeature::ENABLED }
|
||||
|
||||
context 'when signed in' do
|
||||
let(:user) { user_in_group }
|
||||
let(:user) do
|
||||
user_in_group.ensure_feed_token
|
||||
user_in_group.save!
|
||||
user_in_group
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
context 'when signed out' do
|
||||
let(:user) { nil }
|
||||
|
||||
it_behaves_like "it has an RSS button without an RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
|
||||
it_behaves_like "it has an RSS button without a feed token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ feature 'Group show page' do
|
|||
visit path
|
||||
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
|
||||
let(:path) { group_path('not-exist') }
|
||||
|
@ -29,7 +29,7 @@ feature 'Group show page' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
|
||||
context 'when group has a public project', :js do
|
||||
|
|
65
spec/features/ics/dashboard_issues_spec.rb
Normal file
65
spec/features/ics/dashboard_issues_spec.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
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
|
67
spec/features/ics/group_issues_spec.rb
Normal file
67
spec/features/ics/group_issues_spec.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
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
|
66
spec/features/ics/project_issues_spec.rb
Normal file
66
spec/features/ics/project_issues_spec.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
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
|
|||
it "for #{type}" do
|
||||
visit path
|
||||
|
||||
link = find_link('Subscribe')
|
||||
link = find_link('Subscribe to RSS feed')
|
||||
params = CGI.parse(URI.parse(link[:href]).query)
|
||||
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
|
||||
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
|
||||
|
||||
expected = {
|
||||
'rss_token' => [user.rss_token],
|
||||
'feed_token' => [user.feed_token],
|
||||
'milestone_title' => [milestone.title],
|
||||
'assignee_id' => [user.id.to_s]
|
||||
}
|
||||
|
|
|
@ -340,6 +340,20 @@ describe 'Issues' do
|
|||
expect(page).to have_content('baz')
|
||||
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
|
||||
|
||||
describe 'sorting by milestone' do
|
||||
|
|
|
@ -56,21 +56,21 @@ describe 'Profile account page', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'when I reset RSS token' do
|
||||
describe 'when I reset feed token' do
|
||||
before do
|
||||
visit profile_personal_access_tokens_path
|
||||
end
|
||||
|
||||
it 'resets RSS token' do
|
||||
within('.rss-token-reset') do
|
||||
previous_token = find("#rss_token").value
|
||||
it 'resets feed token' do
|
||||
within('.feed-token-reset') do
|
||||
previous_token = find("#feed_token").value
|
||||
|
||||
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
|
||||
|
||||
expect(page).to have_content 'RSS token was successfully reset'
|
||||
expect(page).to have_content 'Feed token was successfully reset'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ feature 'Project Activity RSS' do
|
|||
visit path
|
||||
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
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -23,6 +23,6 @@ feature 'Project Activity RSS' do
|
|||
visit path
|
||||
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
|
||||
|
|
|
@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button without an RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
|
||||
it_behaves_like "it has an RSS button without a feed token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,8 +17,8 @@ feature 'Project Issues RSS' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button with current_user's RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed 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 feed token"
|
||||
end
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -26,7 +26,7 @@ feature 'Project Issues RSS' do
|
|||
visit path
|
||||
end
|
||||
|
||||
it_behaves_like "it has an RSS button without an RSS token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
|
||||
it_behaves_like "it has an RSS button without a feed token"
|
||||
it_behaves_like "an autodiscoverable RSS feed without a feed token"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do
|
|||
visit path
|
||||
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
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do
|
|||
visit path
|
||||
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
|
||||
|
|
|
@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
|
|||
visit path
|
||||
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
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
|
|||
visit path
|
||||
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
|
||||
|
|
|
@ -10,7 +10,7 @@ feature 'User RSS' do
|
|||
visit path
|
||||
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
|
||||
|
||||
context 'when signed out' do
|
||||
|
@ -18,6 +18,6 @@ feature 'User RSS' do
|
|||
visit path
|
||||
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
|
||||
|
|
20
spec/helpers/calendar_helper_spec.rb
Normal file
20
spec/helpers/calendar_helper_spec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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'
|
|||
describe RssHelper do
|
||||
describe '#rss_url_options' 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)
|
||||
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
|
||||
|
||||
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)
|
||||
expect(helper.rss_url_options[:rss_token]).to be_nil
|
||||
expect(helper.rss_url_options[:feed_token]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do
|
|||
|
||||
describe '#find_sessionless_user' do
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
it 'returns rss_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)
|
||||
it 'returns feed_token user if no access_token user found' do
|
||||
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
|
||||
|
||||
it 'returns nil if no user found' do
|
||||
|
|
|
@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#find_user_from_rss_token' do
|
||||
describe '#find_user_from_feed_token' do
|
||||
context 'when the request format is atom' do
|
||||
before do
|
||||
env['HTTP_ACCEPT'] = 'application/atom+xml'
|
||||
end
|
||||
|
||||
it 'returns user if valid rss_token' do
|
||||
set_param(:rss_token, user.rss_token)
|
||||
context 'when feed_token param is provided' do
|
||||
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
|
||||
|
||||
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')
|
||||
|
||||
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil if rss_token is blank' do
|
||||
expect(find_user_from_rss_token).to be_nil
|
||||
end
|
||||
context 'when rss_token param is provided' do
|
||||
it 'returns user if valid rssd_token' do
|
||||
set_param(:rss_token, user.feed_token)
|
||||
|
||||
it 'returns exception if invalid rss_token' do
|
||||
set_param(:rss_token, 'invalid_token')
|
||||
expect(find_user_from_feed_token).to eq user
|
||||
end
|
||||
|
||||
expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
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
|
||||
|
||||
context 'when the request format is not atom' 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
|
||||
|
||||
|
@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do
|
|||
it 'the method call does not modify the original value' do
|
||||
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
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do
|
|||
end
|
||||
|
||||
describe User, 'TokenAuthenticatable' do
|
||||
let(:token_field) { :rss_token }
|
||||
let(:token_field) { :feed_token }
|
||||
it_behaves_like 'TokenAuthenticatable'
|
||||
|
||||
describe 'ensures authentication token' do
|
||||
|
|
|
@ -644,13 +644,13 @@ describe User do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'rss token' do
|
||||
it 'ensures an rss token on read' do
|
||||
user = create(:user, rss_token: nil)
|
||||
rss_token = user.rss_token
|
||||
describe 'feed token' do
|
||||
it 'ensures a feed token on read' do
|
||||
user = create(:user, feed_token: nil)
|
||||
feed_token = user.feed_token
|
||||
|
||||
expect(rss_token).not_to be_blank
|
||||
expect(user.reload.rss_token).to eq rss_token
|
||||
expect(feed_token).not_to be_blank
|
||||
expect(user.reload.feed_token).to eq feed_token
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do
|
|||
end
|
||||
|
||||
def rss_url(user)
|
||||
"/dashboard/projects.atom?rss_token=#{user.rss_token}"
|
||||
"/dashboard/projects.atom?feed_token=#{user.feed_token}"
|
||||
end
|
||||
|
||||
def private_token_headers(user)
|
||||
|
|
|
@ -162,8 +162,8 @@ describe ProfilesController, "routing" do
|
|||
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
|
||||
end
|
||||
|
||||
it "to #reset_rss_token" do
|
||||
expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
|
||||
it "to #reset_feed_token" do
|
||||
expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token')
|
||||
end
|
||||
|
||||
it "to #show" do
|
||||
|
@ -249,7 +249,11 @@ describe DashboardController, "routing" do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
it "to #merge_requests" do
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
|
||||
it "has an RSS autodiscovery link tag with current_user's RSS token" do
|
||||
expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false)
|
||||
shared_examples "an autodiscoverable RSS feed with current_user's feed 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*='feed_token=#{user.feed_token}']", visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "it has an RSS button with current_user's RSS token" do
|
||||
it "shows the RSS button with current_user's RSS token" do
|
||||
expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']")
|
||||
shared_examples "it has an RSS button with current_user's feed token" do
|
||||
it "shows the RSS button with current_user's feed token" do
|
||||
expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "an autodiscoverable RSS feed without an RSS token" do
|
||||
it "has an RSS autodiscovery link tag without an RSS token" do
|
||||
expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
|
||||
shared_examples "an autodiscoverable RSS feed without a feed 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*='feed_token'])", visible: false)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "it has an RSS button without an RSS token" do
|
||||
it "shows the RSS button without an RSS token" do
|
||||
expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
|
||||
shared_examples "it has an RSS button without a feed token" do
|
||||
it "shows the RSS button without a feed token" do
|
||||
expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,9 +13,9 @@ describe 'tokens rake tasks' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'reset_all_rss task' do
|
||||
describe 'reset_all_feed 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
|
||||
|
|
Loading…
Reference in a new issue