Compare commits

..

21 Commits
1.0.3 ... 1.0.4

Author SHA1 Message Date
Jean-Philippe Lang
d3ae4cfd11 tagged version 1.0.4
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.0.4@4448 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-28 12:57:17 +00:00
Jean-Philippe Lang
ed52753a12 Updates for 1.0.4 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4447 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-28 12:47:36 +00:00
Jean-Philippe Lang
bac64c9ab4 Backported r4441 to r4444 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4445 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 16:45:07 +00:00
Jean-Philippe Lang
b58382ef2e Merged r4386 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4440 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 15:48:30 +00:00
Jean-Philippe Lang
541a371b41 Backported r4357, r4358, r4360 and r4363 to r4367 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4439 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 15:16:26 +00:00
Jean-Philippe Lang
bdb888476d Merged r4437 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4438 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:47:31 +00:00
Jean-Philippe Lang
f5f55359a8 Translations updates.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4436 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:40:51 +00:00
Jean-Philippe Lang
eb590ce1eb Merged r4428 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4435 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:31:29 +00:00
Jean-Philippe Lang
db64675310 Merged r4431 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4434 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:30:41 +00:00
Jean-Philippe Lang
92b37c0bea Merged r4426 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-27 14:28:51 +00:00
Jean-Philippe Lang
faa461c66a Merged r4422 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4423 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-21 14:26:01 +00:00
Jean-Philippe Lang
b5a409791c Merged r4419 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4420 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 14:12:35 +00:00
Jean-Philippe Lang
6c362a5a76 Merged r4417 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4418 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 14:06:32 +00:00
Jean-Philippe Lang
dbb26b08f8 Merged r4414 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4415 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-20 10:23:13 +00:00
Jean-Philippe Lang
2a7a95d746 Backported r4397 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:30:53 +00:00
Jean-Philippe Lang
c70ae6c735 Merged r4380 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:18:23 +00:00
Jean-Philippe Lang
e174c10d6d Merged r4385 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:17:20 +00:00
Jean-Philippe Lang
2b9a5bf6e2 Merged r4378 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4398 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-14 11:15:53 +00:00
Jean-Philippe Lang
d3b536e9a6 Merged r4389 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4390 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-09 19:56:29 +00:00
Jean-Philippe Lang
9b1eda1b22 Fixed a test broken by r4355 (#6786).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-01 14:47:23 +00:00
Jean-Philippe Lang
f1f0704ef8 Merged r4354 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.0-stable@4355 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-11-01 13:14:14 +00:00
38 changed files with 938 additions and 759 deletions

View File

@@ -26,7 +26,7 @@ class IssuesController < ApplicationController
before_filter :find_optional_project, :only => [:index]
before_filter :check_for_default_issue_status, :only => [:new, :create]
before_filter :build_new_issue_from_params, :only => [:new, :create]
accept_key_auth :index, :show
accept_key_auth :index, :show, :create, :update, :destroy
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
@@ -300,6 +300,7 @@ private
render_error l(:error_no_tracker_in_project)
return false
end
@issue.start_date ||= Date.today
if params[:issue].is_a?(Hash)
@issue.safe_attributes = params[:issue]
if User.current.allowed_to?(:add_issue_watchers, @project) && @issue.new_record?
@@ -307,7 +308,6 @@ private
end
end
@issue.author = User.current
@issue.start_date ||= Date.today
@priorities = IssuePriority.all
@allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
end

View File

@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_key_auth :index
accept_key_auth :index, :show, :create, :update, :destroy
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post?
@@ -93,7 +93,7 @@ class ProjectsController < ApplicationController
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
}
format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end
else
respond_to do |format|

View File

@@ -109,6 +109,10 @@ class VersionsController < ApplicationController
if @version.update_attributes(attributes)
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
else
respond_to do |format|
format.html { render :action => 'edit' }
end
end
end
end

View File

@@ -18,7 +18,7 @@
class Board < ActiveRecord::Base
belongs_to :project
has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :delete_all, :order => "#{Message.table_name}.created_on DESC"
has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC"
belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id
acts_as_list :scope => :project_id
acts_as_watchable

View File

@@ -31,6 +31,7 @@ class Group < Principal
def user_added(user)
members.each do |member|
next if member.project.nil?
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)

View File

@@ -237,7 +237,7 @@ class Issue < ActiveRecord::Base
if !user.allowed_to?(:manage_subtasks, project)
attrs.delete('parent_issue_id')
elsif !attrs['parent_issue_id'].blank?
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'])
attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
end
end

View File

@@ -2,5 +2,5 @@
<div class="box">
<p><%= f.text_field :name, :size => 30, :required => true %></p>
<p><%= f.select :assigned_to_id, @project.users.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
<p><%= f.select :assigned_to_id, @project.users.sort.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
</div>

View File

@@ -5,9 +5,9 @@ function recreateSortables() {
Sortable.destroy('list-left');
Sortable.destroy('list-right');
Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=top', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=left', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('/my/order_blocks?group=right', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-top", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'top') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-top")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-left", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'left') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-left")})}, only:'mypage-box', tag:'div'})
Sortable.create("list-right", {constraint:false, containment:['list-top','list-left','list-right'], dropOnEmpty:true, handle:'handle', onUpdate:function(){new Ajax.Request('<%= url_for(:controller => 'my', :action => 'order_blocks', :group => 'right') %>', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("list-right")})}, only:'mypage-box', tag:'div'})
}
function updateSelect() {

View File

@@ -6,7 +6,7 @@
<p><%= setting_text_area :welcome_text, :cols => 60, :rows => 5, :class => 'wiki-edit' %></p>
<%= wikitoolbar_for 'settings_welcome_text' %>
<p><%= setting_text_field :attachment_max_size, :size => 6 %> KB</p>
<p><%= setting_text_field :attachment_max_size, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_field :per_page_options, :size => 20 %><br />
<em><%= l(:text_comma_separated) %></em></p>
@@ -26,7 +26,7 @@
<p><%= setting_text_field :feeds_limit, :size => 6 %></p>
<p><%= setting_text_field :file_max_size_displayed, :size => 6 %> KB</p>
<p><%= setting_text_field :file_max_size_displayed, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_field :diff_max_lines_displayed, :size => 6 %></p>

View File

@@ -5,6 +5,7 @@
<% if @workflow_counts.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<div class="autoscroll">
<table class="list">
<thead>
<tr>
@@ -30,4 +31,5 @@
<% end -%>
</tbody>
</table>
</div>
<% end %>

View File

@@ -84,7 +84,7 @@ ActionMailer::Base.send :include, AsynchronousMailer
module I18n
module Backend
module Base
def warn_syntax_deprecation!
def warn_syntax_deprecation!(*args)
return if @skip_syntax_deprecation
warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\nDowngrade your i18n gem to 0.3.7 (everything above must be deinstalled) to remove this warning, see http://www.redmine.org/issues/5608 for more information."
@skip_syntax_deprecation = true

View File

@@ -321,7 +321,7 @@ bg:
label_registered_on: Регистрация
label_activity: Дейност
label_new: Нов
label_logged_as: Влязъл като
label_logged_as: Здравейте,
label_environment: Среда
label_authentication: Оторизация
label_auth_source: Начин на оторозация
@@ -850,8 +850,8 @@ bg:
button_duplicate: Дублиране
button_copy_and_follow: Копиране и продължаване
label_copy_source: Източник
setting_issue_done_ratio: Изчисление на процента на готово задачи с
setting_issue_done_ratio_issue_status: Използване на състояниетона задачите
setting_issue_done_ratio: Изчисление на процента на готови задачи с
setting_issue_done_ratio_issue_status: Използване на състоянието на задачите
error_issue_done_ratios_not_updated: Процентът на завършените задачи не е обновен.
error_workflow_copy_target: Моля изберете тракер(и) и роля (роли).
setting_issue_done_ratio_issue_field: Използване на поле 'задача'
@@ -896,28 +896,30 @@ bg:
error_can_not_remove_role: Тази роля се използва и не може да бъде изтрита.
error_can_not_delete_tracker: Този тракер съдържа задачи и не може да бъде изтрит.
field_principal: Principal
label_my_page_block: My page block
label_my_page_block: Блокове в личната страница
notice_failed_to_save_members: "Невъзможност за запис на член(ове): {{errors}}."
text_zoom_out: Намаляване
text_zoom_in: Увеличаване
notice_unable_delete_time_entry: Невъзможност за изтриване на запис на time log.
label_overall_spent_time: Общо употребено време
field_time_entries: Log time
notice_not_authorized_archived_project: The project you're trying to access has been archived.
text_tip_issue_end_day: issue ending this day
field_text: Text field
label_user_mail_option_only_owner: Only for things I am the owner of
field_member_of_group: Assignee's group
project_module_gantt: Gantt
text_are_you_sure_with_children: Delete issue and all child issues?
text_tip_issue_begin_end_day: issue beginning and ending this day
setting_default_notification_option: Default notification option
project_module_calendar: Calendar
label_user_mail_option_only_my_events: Only for things I watch or I'm involved in
text_tip_issue_begin_day: issue beginning this day
label_user_mail_option_only_assigned: Only for things I am assigned to
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}"
notice_not_authorized_archived_project: Проектът, който се опитвате да видите е архивиран.
text_tip_issue_end_day: задача, завършваща този ден
field_text: Текстово поле
label_user_mail_option_only_owner: Само за неща, на които аз съм собственик
field_member_of_group: Член на група
project_module_gantt: Мрежов график
text_are_you_sure_with_children: Изтриване на задачата и нейните подзадачи?
text_tip_issue_begin_end_day: задача, започваща и завършваща този ден
setting_default_notification_option: Подразбиращ се начин за известяване
project_module_calendar: Календар
label_user_mail_option_only_my_events: Само за неща, в които съм включен/а
text_tip_issue_begin_day: задача, започваща този ден
label_user_mail_option_only_assigned: Само за неща, назначени на мен
button_edit_associated_wikipage: "Редактиране на асоциираната Wiki страница: {{page_title}}"
field_assigned_to_role: Assignee's role
field_start_date: Start date
label_principal_search: "Search for user or group:"
label_user_search: "Search for user:"
field_start_date: Начална дата
label_principal_search: "Търсене на потребител или група:"
label_user_search: "Търсене на потребител:"
field_visible: Видим
setting_emails_header: Emails header

View File

@@ -370,7 +370,7 @@ da:
label_reported_issues: Rapporterede sager
label_assigned_to_me_issues: Sager tildelt mig
label_last_login: Sidste login tidspunkt
label_registered_on: Registeret den
label_registered_on: Registreret den
label_activity: Aktivitet
label_new: Ny
label_logged_as: Registreret som
@@ -441,7 +441,7 @@ da:
label_none: intet
label_nobody: ingen
label_next: Næste
label_previous: Forrig
label_previous: Forrige
label_used_by: Brugt af
label_details: Detaljer
label_add_note: Tilføj note
@@ -537,7 +537,7 @@ da:
label_view_diff: Vis forskelle
label_diff_inline: inline
label_diff_side_by_side: side om side
label_options: Optioner
label_options: Formatering
label_copy_workflow_from: Kopier arbejdsgang fra
label_permissions_report: Godkendelsesrapport
label_watched_issues: Overvågede sager
@@ -547,7 +547,7 @@ da:
label_relation_new: Ny relation
label_relation_delete: Slet relation
label_relates_to: relaterer til
label_duplicates: kopierer
label_duplicates: duplikater
label_blocks: blokerer
label_blocked_by: blokeret af
label_precedes: kommer før
@@ -870,8 +870,8 @@ da:
label_version_sharing_tree: Med projekt træ
label_version_sharing_none: Ikke delt
error_can_not_archive_project: Dette projekt kan ikke arkiveres
button_duplicate: Kopier
button_copy_and_follow: Kopier og overvåg
button_duplicate: Duplikér
button_copy_and_follow: Kopiér og overvåg
label_copy_source: Kilde
setting_issue_done_ratio: Beregn sagsløsning ratio
setting_issue_done_ratio_issue_status: Benyt sags status
@@ -896,14 +896,14 @@ da:
label_missing_feeds_access_key: Mangler en RSS nøgle
button_show: Vis
text_line_separated: Flere væredier tilladt (en linje for hver værdi).
setting_mail_handler_body_delimiters: Trunker emails efter en af disse linjer
setting_mail_handler_body_delimiters: Trunkér emails efter en af disse linjer
permission_add_subprojects: Lav underprojekter
label_subproject_new: Nyt underprojekt
text_own_membership_delete_confirmation: |-
Du er ved at fjerne en eller flere af dine rettigheder, og kan muligvis ikke redigere projektet bagefter.
Er du sikker på du ønsker at fortsætte?
label_close_versions: Luk færdige versioner
label_board_sticky: Sticky
label_board_sticky: Klistret
label_board_locked: Låst
permission_export_wiki_pages: Eksporter wiki sider
setting_cache_formatted_text: Cache formatteret tekst
@@ -914,25 +914,25 @@ da:
field_parent_issue: Hoved opgave
label_subtask_plural: Under opgaver
label_project_copy_notifications: Send email notifikationer, mens projektet kopieres
error_can_not_delete_custom_field: Unable to delete custom field
error_unable_to_connect: Unable to connect ({{value}})
error_can_not_remove_role: This role is in use and can not be deleted.
error_can_not_delete_tracker: This tracker contains issues and can't be deleted.
error_can_not_delete_custom_field: Kan ikke slette brugerdefineret felt
error_unable_to_connect: Kan ikke forbinde ({{value}})
error_can_not_remove_role: Denne rolle er i brug og kan ikke slettes.
error_can_not_delete_tracker: Denne type indeholder sager og kan ikke slettes.
field_principal: Principal
label_my_page_block: My page block
notice_failed_to_save_members: "Failed to save member(s): {{errors}}."
text_zoom_out: Zoom out
label_my_page_block: blok
notice_failed_to_save_members: "Fejl under lagring af medlem(mer): {{errors}}."
text_zoom_out: Zoom ud
text_zoom_in: Zoom in
notice_unable_delete_time_entry: Unable to delete time log entry.
label_overall_spent_time: Overall spent time
field_time_entries: Log time
notice_unable_delete_time_entry: Kan ikke slette tidsregistrering.
label_overall_spent_time: Overordnet forbrug af tid
field_time_entries: Log tid
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}"
text_are_you_sure_with_children: Delete issue and all child issues?
field_text: Text field
project_module_calendar: Kalender
field_member_of_group: Medlem af Gruppe
field_assigned_to_role: Medlem af Rolle
button_edit_associated_wikipage: "Redigér tilknyttet Wiki side: {{page_title}}"
text_are_you_sure_with_children: Slet sag og alle undersager?
field_text: Tekstfelt
field_start_date: Start date
label_principal_search: "Search for user or group:"

View File

@@ -635,7 +635,6 @@ es:
label_user_activity: "Actividad de {{value}}"
label_user_mail_no_self_notified: "No quiero ser avisado de cambios hechos por mí"
label_user_mail_option_all: "Para cualquier evento en todos mis proyectos"
label_user_mail_option_none: "Sólo para elementos monitorizados o relacionados conmigo"
label_user_mail_option_selected: "Para cualquier evento de los proyectos seleccionados..."
label_user_new: Nuevo usuario
label_user_plural: Usuarios
@@ -854,12 +853,12 @@ es:
text_wiki_page_destroy_children: Eliminar páginas hijas y todos sus descendientes
setting_password_min_length: Longitud mínima de la contraseña
field_group_by: Agrupar resultados por
mail_subject_wiki_content_updated: "La página wiki '{{page}}' ha sido actualizada"
mail_subject_wiki_content_updated: "La página wiki '{{id}}' ha sido actualizada"
label_wiki_content_added: Página wiki añadida
mail_subject_wiki_content_added: "Se ha añadido la página wiki '{{page}}'."
mail_body_wiki_content_added: "{{author}} ha añadido la página wiki '{{page}}'."
mail_subject_wiki_content_added: "Se ha añadido la página wiki '{{id}}'."
mail_body_wiki_content_added: "{{author}} ha añadido la página wiki '{{id}}'."
label_wiki_content_updated: Página wiki actualizada
mail_body_wiki_content_updated: La página wiki '{{page}}' ha sido actualizada por {{author}}.
mail_body_wiki_content_updated: La página wiki '{{id}}' ha sido actualizada por {{author}}.
permission_add_project: Crear proyecto
setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos
label_view_all_revisions: Ver todas las revisiones
@@ -951,13 +950,20 @@ es:
label_overall_spent_time: Tiempo total dedicado
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role
button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}"
text_are_you_sure_with_children: Delete issue and all child issues?
field_text: Text field
field_start_date: Start date
label_principal_search: "Search for user or group:"
label_user_search: "Search for user:"
project_module_calendar: Calendario
button_edit_associated_wikipage: "Editar paginas Wiki asociadas: {{page_title}}"
text_are_you_sure_with_children: ¿Borrar peticiones y todas sus peticiones hijas?
field_text: Campo de texto
label_user_mail_option_only_owner: Solo para objetos que soy propietario
setting_default_notification_option: Opcion de notificacion por defecto
label_user_mail_option_only_my_events: Solo para objetos que soy seguidor o estoy involucrado
label_user_mail_option_only_assigned: Solo para objetos que estoy asignado
label_user_mail_option_none: Sin eventos
field_member_of_group: Asignado al grupo
field_assigned_to_role: Asignado al perfil
notice_not_authorized_archived_project: El proyecto al que intenta acceder ha sido archivado.
field_start_date: Fecha de inicio
label_principal_search: "Buscar por usuario o grupo:"
label_user_search: "Buscar por usuario:"
field_visible: Visible
setting_emails_header: Encabezado de Correos

View File

@@ -288,6 +288,7 @@ ja:
field_attr_lastname: 苗字属性
field_attr_mail: メール属性
field_onthefly: あわせてユーザを作成
field_start_date: 開始日
field_done_ratio: 進捗 %
field_auth_source: 認証方式
field_hide_mail: メールアドレスを隠す
@@ -322,6 +323,7 @@ ja:
field_member_of_group: 担当者のグループ
field_assigned_to_role: 担当者のロール
field_text: テキスト
field_visible: 表示
setting_app_title: アプリケーションのタイトル
setting_app_subtitle: アプリケーションのサブタイトル
@@ -351,6 +353,7 @@ ja:
setting_issue_list_default_columns: チケットの一覧で表示する項目
setting_repositories_encodings: リポジトリのエンコーディング
setting_commit_logs_encoding: コミットメッセージのエンコーディング
setting_emails_header: メールのヘッダ
setting_emails_footer: メールのフッタ
setting_protocol: プロトコル
setting_per_page_options: ページ毎の表示件数
@@ -806,6 +809,8 @@ ja:
label_api_access_key_created_on: "APIアクセスキーは{{value}}前に作成されました"
label_subtask_plural: 子チケット
label_project_copy_notifications: コピーしたチケットのメール通知を送信する
label_principal_search: "ユーザまたはグループの検索:"
label_user_search: "ユーザの検索:"
button_login: ログイン
button_submit: 変更

View File

@@ -308,6 +308,7 @@ sv:
field_attr_lastname: Efternamnsattribut
field_attr_mail: Mailattribut
field_onthefly: Skapa användare on-the-fly
field_start_date: Startdatum
field_done_ratio: % Klart
field_auth_source: Autentiseringsläge
field_hide_mail: Dölj min mailadress
@@ -823,6 +824,8 @@ sv:
label_profile: Profil
label_subtask_plural: Underaktiviteter
label_project_copy_notifications: Skicka mailnotifieringar när projektet kopieras
label_principal_search: "Sök efter användare eller grupp:"
label_user_search: "Sök efter användare:"
button_login: Logga in
button_submit: Skicka
@@ -891,9 +894,9 @@ sv:
text_journal_set_to: "{{label}} satt till {{value}}"
text_journal_deleted: "{{label}} borttagen ({{old}})"
text_journal_added: "{{label}} {{value}} tillagd"
text_tip_issue_begin_day: arbetsuppgift som börjar denna dag
text_tip_issue_end_day: arbetsuppgift som slutar denna dag
text_tip_issue_begin_end_day: arbetsuppgift börjar och slutar denna dag
text_tip_issue_begin_day: ärende som börjar denna dag
text_tip_issue_end_day: ärende som slutar denna dag
text_tip_issue_begin_end_day: ärende som börjar och slutar denna dag
text_project_identifier_info: 'Små bokstäver (a-z), siffror och streck tillåtna.<br />När den är sparad kan identifieraren inte ändras.'
text_caracters_maximum: "max {{count}} tecken."
text_caracters_minimum: "Måste vara minst {{count}} tecken lång."

View File

@@ -177,7 +177,7 @@
greater_than_start_date: "必須在開始日期之後"
not_same_project: "不屬於同一個專案"
circular_dependency: "這個關聯會導致環狀相依"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
cant_link_an_issue_with_a_descendant: "項目無法被連結至自己的子項目"
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@@ -278,10 +278,10 @@
mail_body_account_activation_request: "有位新用戶 ({{value}}) 已經完成註冊,正等候您的審核:"
mail_subject_reminder: "您有 {{count}} 個項目即將到期 ({{days}})"
mail_body_reminder: "{{count}} 個指派給您的項目,將於 {{days}} 天之內到期:"
mail_subject_wiki_content_added: "'{{page}}' wiki 頁面已被新增"
mail_body_wiki_content_added: "The '{{page}}' wiki 頁面已被 {{author}} 新增。"
mail_subject_wiki_content_updated: "'{{page}}' wiki 頁面已被更新"
mail_body_wiki_content_updated: "The '{{page}}' wiki 頁面已被 {{author}} 更新。"
mail_subject_wiki_content_added: "'{{id}}' wiki 頁面已被新增"
mail_body_wiki_content_added: "The '{{id}}' wiki 頁面已被 {{author}} 新增。"
mail_subject_wiki_content_updated: "'{{id}}' wiki 頁面已被更新"
mail_body_wiki_content_updated: "The '{{id}}' wiki 頁面已被 {{author}} 更新。"
gui_validation_error: 1 個錯誤
gui_validation_error_plural: "{{count}} 個錯誤"
@@ -383,6 +383,7 @@
field_member_of_group: "被指派者的群組"
field_assigned_to_role: "被指派者的角色"
field_text: 內容文字
field_visible: 可被看見
setting_app_title: 標題
setting_app_subtitle: 副標題
@@ -411,6 +412,7 @@
setting_issue_list_default_columns: 預設顯示於項目清單的欄位
setting_repositories_encodings: 版本庫編碼
setting_commit_logs_encoding: 送交訊息編碼
setting_emails_header: 電子郵件前頭說明
setting_emails_footer: 電子郵件附帶說明
setting_protocol: 協定
setting_per_page_options: 每頁顯示個數選項
@@ -813,7 +815,7 @@
label_search_titles_only: 僅搜尋標題
label_user_mail_option_all: "提醒與我的專案有關的全部事件"
label_user_mail_option_selected: "只提醒我所選擇專案中的事件..."
label_user_mail_option_none: "只提醒我觀察中或參與中的事件"
label_user_mail_option_none: "取消提醒"
label_user_mail_option_only_my_events: "只提醒我觀察中或參與中的事物"
label_user_mail_option_only_assigned: "只提醒我被指派的事物"
label_user_mail_option_only_owner: "只提醒我作為擁有者的事物"
@@ -867,6 +869,8 @@
label_profile: 配置概況
label_subtask_plural: 子工作項目
label_project_copy_notifications: 在複製專案的過程中,傳送通知郵件
label_principal_search: "搜尋用戶或群組:"
label_user_search: "搜尋用戶:"
button_login: 登入
button_submit: 送出
@@ -1009,5 +1013,3 @@
enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動
label_principal_search: "Search for user or group:"
label_user_search: "Search for user:"

View File

@@ -4,6 +4,31 @@ Redmine - project management software
Copyright (C) 2006-2010 Jean-Philippe Lang
http://www.redmine.org/
== 2010-11-28 v1.0.4
* #5324: Git not working if color.ui is enabled
* #6447: Issues API doesn't allow full key auth for all actions
* #6457: Edit User group problem
* #6575: start date being filled with current date even when blank value is submitted
* #6740: Max attachment size, incorrect usage of 'KB'
* #6760: Select box sorted by ID instead of name in Issue Category
* #6766: Changing target version name can cause an internal error
* #6784: Redmine not working with i18n gem 0.4.2
* #6839: Hardcoded absolute links in my/page_layout
* #6841: Projects API doesn't allow full key auth for all actions
* #6860: svn: Write error: Broken pipe when browsing repository
* #6874: API should return XML description when creating a project
* #6932: submitting wrong parent task input creates a 500 error
* #6966: Records of Forums are remained, deleting project
* #6990: Layout problem in workflow overview
* #5117: mercurial_adapter should ensure the right LANG environment variable
* #6782: Traditional Chinese language file (to r4352)
* #6783: Swedish Translation for r4352
* #6804: Bugfix: spelling fixes
* #6814: Japanese Translation for r4362
* #6948: Bulgarian translation
* #6973: Update es.yml
== 2010-10-31 v1.0.3
* #4065: Redmine.pm doesn't work with LDAPS and a non-standard port
@@ -1112,7 +1137,7 @@ http://www.redmine.org/
* Search engines now supports pagination. Results are sorted in reverse chronological order
* Added "Estimated hours" attribute on issues
* A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category
* Forum notifications are now also sent to the authors of the thread, even if they dont watch the board
* Forum notifications are now also sent to the authors of the thread, even if they don<EFBFBD>t watch the board
* Added an application setting to specify the application protocol (http or https) used to generate urls in emails
* Gantt chart: now starts at the current month by default
* Gantt chart: month count and zoom factor are automatically saved as user preferences
@@ -1120,7 +1145,7 @@ http://www.redmine.org/
* Added wiki index by date
* Added preview on add/edit issue form
* Emails footer can now be customized from the admin interface (Admin -> Email notifications)
* Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyre properly displayed)
* Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that they<EFBFBD>re properly displayed)
* Calendar: first day of week can now be set in lang files
* Automatic closing of duplicate issues
* Added a cross-project issue list
@@ -1132,7 +1157,7 @@ http://www.redmine.org/
* Added some accesskeys
* Added "Float" as a custom field format
* Added basic Theme support
* Added the ability to set the done ratio of issues fixed by commit (Nikolay Solakov)
* Added the ability to set the <EFBFBD>done ratio<EFBFBD> of issues fixed by commit (Nikolay Solakov)
* Added custom fields in issue related mail notifications
* Email notifications are now sent in plain text and html
* Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed.
@@ -1165,7 +1190,7 @@ http://www.redmine.org/
* Added Korean translation (Choi Jong Yoon)
* Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations
* Performance improvement on calendar and gantt
* Fixed: wiki preview doesnt work on long entries
* Fixed: wiki preview doesn<EFBFBD>t work on long entries
* Fixed: queries with multiple custom fields return no result
* Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters
* Fixed: URL with ~ broken in wiki formatting
@@ -1176,7 +1201,7 @@ http://www.redmine.org/
* per project forums added
* added the ability to archive projects
* added Watch functionality on issues. It allows users to receive notifications about issue changes
* added <EFBFBD>Watch<EFBFBD> functionality on issues. It allows users to receive notifications about issue changes
* custom fields for issues can now be used as filters on issue list
* added per user custom queries
* commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings)
@@ -1217,7 +1242,7 @@ http://www.redmine.org/
* added swedish translation (Thomas Habets)
* italian translation update (Alessio Spadaro)
* japanese translation update (Satoru Kurashiki)
* fixed: error on history atom feed when theres no notes on an issue change
* fixed: error on history atom feed when there<EFBFBD>s no notes on an issue change
* fixed: error in journalizing an issue with longtext custom fields (Postgresql)
* fixed: creation of Oracle schema
* fixed: last day of the month not included in project activity

View File

@@ -211,7 +211,7 @@ module Redmine
if identifier_to
cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
else
cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
end
cmd << " -- #{shell_quote path}" unless path.empty?
@@ -255,7 +255,7 @@ module Redmine
if identifier.nil?
identifier = 'HEAD'
end
cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
cat = nil
shellout(cmd) do |io|
io.binmode

View File

@@ -38,13 +38,13 @@ module Redmine
# release number (eg 0.9.5 or 1.0) or as a revision
# id composed of 12 hexa characters.
theversion = hgversion_from_command_line
if theversion.match(/^\d+(\.\d+)+/)
theversion.split(".").collect(&:to_i)
if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)})
m[2].scan(%r{\d+}).collect(&:to_i)
end
end
def hgversion_from_command_line
%x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1]
shellout("#{HG_BIN} --version") { |io| io.read }.to_s
end
def template_path

View File

@@ -36,8 +36,8 @@ module Redmine
version = nil
shellout(cmd) do |io|
# Read svn version in first returned line
if m = io.gets.to_s.match(%r{((\d+\.)+\d+)})
version = m[0].scan(%r{\d+}).collect(&:to_i)
if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)})
version = m[2].scan(%r{\d+}).collect(&:to_i)
end
end
return nil if $? && $?.exitstatus != 0

View File

@@ -4,7 +4,7 @@ module Redmine
module VERSION #:nodoc:
MAJOR = 1
MINOR = 0
TINY = 3
TINY = 4
# Branch values:
# * official release: nil

View File

@@ -145,3 +145,15 @@ attachments_012:
filename: version_file.zip
author_id: 2
content_type: application/octet-stream
attachments_013:
created_on: 2006-07-19 21:07:27 +02:00
container_type: Message
container_id: 1
downloads: 0
disk_filename: 060719210727_foo.zip
digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
id: 13
filesize: 452
filename: foo.zip
author_id: 2
content_type: application/octet-stream

View File

@@ -382,6 +382,7 @@ class IssuesControllerTest < ActionController::TestCase
:subject => 'This is the test_new issue',
:description => 'This is the description',
:priority_id => 5,
:start_date => '2010-11-07',
:estimated_hours => '',
:custom_field_values => {'2' => 'Value for field 2'}}
end
@@ -392,12 +393,33 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 2, issue.author_id
assert_equal 3, issue.tracker_id
assert_equal 2, issue.status_id
assert_equal Date.parse('2010-11-07'), issue.start_date
assert_nil issue.estimated_hours
v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
assert_not_nil v
assert_equal 'Value for field 2', v.value
end
def test_post_create_without_start_date
@request.session[:user_id] = 2
assert_difference 'Issue.count' do
post :create, :project_id => 1,
:issue => {:tracker_id => 3,
:status_id => 2,
:subject => 'This is the test_new issue',
:description => 'This is the description',
:priority_id => 5,
:start_date => '',
:estimated_hours => '',
:custom_field_values => {'2' => 'Value for field 2'}}
end
assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
issue = Issue.find_by_subject('This is the test_new issue')
assert_not_nil issue
assert_nil issue.start_date
end
def test_post_create_and_continue
@request.session[:user_id] = 2
post :create, :project_id => 1,
@@ -476,6 +498,20 @@ class IssuesControllerTest < ActionController::TestCase
assert_not_nil issue
assert_equal Issue.find(2), issue.parent
end
def test_post_create_subissue_with_non_numeric_parent_id
@request.session[:user_id] = 2
assert_difference 'Issue.count' do
post :create, :project_id => 1,
:issue => {:tracker_id => 1,
:subject => 'This is a child issue',
:parent_issue_id => 'ABC'}
end
issue = Issue.find_by_subject('This is a child issue')
assert_not_nil issue
assert_nil issue.parent
end
def test_post_create_should_send_a_notification
ActionMailer::Base.deliveries.clear

View File

@@ -123,6 +123,15 @@ class VersionsControllerTest < ActionController::TestCase
assert_equal 'New version name', version.name
assert_equal Date.today, version.effective_date
end
def test_post_update_with_validation_failure
@request.session[:user_id] = 2
post :update, :id => 2,
:version => { :name => '',
:effective_date => Date.today.strftime("%Y-%m-%d")}
assert_response :success
assert_template 'edit'
end
def test_destroy
@request.session[:user_id] = 2

View File

@@ -1,6 +1,6 @@
require "#{File.dirname(__FILE__)}/../test_helper"
require "#{File.dirname(__FILE__)}/../../test_helper"
class DisabledRestApi < ActionController::IntegrationTest
class ApiTest::DisabledRestApiTest < ActionController::IntegrationTest
fixtures :all
def setup

View File

@@ -0,0 +1,31 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::HttpBasicLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
setup do
project = Project.find('onlinestore')
EnabledModule.create(:project => project, :name => 'news')
end
context "in :xml format" do
should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.xml")
end
context "in :json format" do
should_allow_http_basic_auth_with_username_and_password(:get, "/projects/onlinestore/news.json")
end
end
end

View File

@@ -0,0 +1,27 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
should_allow_http_basic_auth_with_key(:get, "/news.xml")
end
context "in :json format" do
should_allow_http_basic_auth_with_key(:get, "/news.json")
end
end
end

View File

@@ -0,0 +1,336 @@
# Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::IssuesTest < ActionController::IntegrationTest
fixtures :projects,
:users,
:roles,
:members,
:member_roles,
:issues,
:issue_statuses,
:versions,
:trackers,
:projects_trackers,
:issue_categories,
:enabled_modules,
:enumerations,
:attachments,
:workflows,
:custom_fields,
:custom_values,
:custom_fields_projects,
:custom_fields_trackers,
:time_entries,
:journals,
:journal_details,
:queries
def setup
Setting.rest_api_enabled = '1'
end
# Use a private project to make sure auth is really working and not just
# only showing public issues.
context "/index.xml" do
should_allow_api_authentication(:get, "/projects/private-child/issues.xml")
end
context "/index.json" do
should_allow_api_authentication(:get, "/projects/private-child/issues.json")
end
context "/index.xml with filter" do
should_allow_api_authentication(:get, "/projects/private-child/issues.xml?status_id=5")
should "show only issues with the status_id" do
get '/issues.xml?status_id=5'
assert_tag :tag => 'issues',
:children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
:only => { :tag => 'issue' } }
end
end
context "/index.json with filter" do
should_allow_api_authentication(:get, "/projects/private-child/issues.json?status_id=5")
should "show only issues with the status_id" do
get '/issues.json?status_id=5'
json = ActiveSupport::JSON.decode(response.body)
status_ids_used = json.collect {|j| j['status_id'] }
assert_equal 3, status_ids_used.length
assert status_ids_used.all? {|id| id == 5 }
end
end
# Issue 6 is on a private project
context "/issues/6.xml" do
should_allow_api_authentication(:get, "/issues/6.xml")
end
context "/issues/6.json" do
should_allow_api_authentication(:get, "/issues/6.json")
end
context "POST /issues.xml" do
should_allow_api_authentication(:post,
'/issues.xml',
{:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
{:success_code => :created})
should "create an issue with the attributes" do
assert_difference('Issue.count') do
post '/issues.xml', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
end
issue = Issue.first(:order => 'id DESC')
assert_equal 1, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 3, issue.status_id
assert_equal 'API test', issue.subject
end
end
context "POST /issues.xml with failure" do
should_allow_api_authentication(:post,
'/issues.xml',
{:issue => {:project_id => 1}},
{:success_code => :unprocessable_entity})
should "have an errors tag" do
assert_no_difference('Issue.count') do
post '/issues.xml', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
end
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "POST /issues.json" do
should_allow_api_authentication(:post,
'/issues.json',
{:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}},
{:success_code => :created})
should "create an issue with the attributes" do
assert_difference('Issue.count') do
post '/issues.json', {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, :authorization => credentials('jsmith')
end
issue = Issue.first(:order => 'id DESC')
assert_equal 1, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 3, issue.status_id
assert_equal 'API test', issue.subject
end
end
context "POST /issues.json with failure" do
should_allow_api_authentication(:post,
'/issues.json',
{:issue => {:project_id => 1}},
{:success_code => :unprocessable_entity})
should "have an errors element" do
assert_no_difference('Issue.count') do
post '/issues.json', {:issue => {:project_id => 1}}, :authorization => credentials('jsmith')
end
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
# Issue 6 is on a private project
context "PUT /issues/6.xml" do
setup do
@parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.xml',
{:issue => {:subject => 'API update', :notes => 'A new note'}},
{:success_code => :ok})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "create a new journal" do
assert_difference('Journal.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "add the note to the journal" do
put '/issues/6.xml', @parameters, @headers
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
put '/issues/6.xml', @parameters, @headers
issue = Issue.find(6)
assert_equal "API update", issue.subject
end
end
context "PUT /issues/6.xml with failed update" do
setup do
@parameters = {:issue => {:subject => ''}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.xml',
{:issue => {:subject => ''}}, # Missing subject should fail
{:success_code => :unprocessable_entity})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "not create a new journal" do
assert_no_difference('Journal.count') do
put '/issues/6.xml', @parameters, @headers
end
end
should "have an errors tag" do
put '/issues/6.xml', @parameters, @headers
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "PUT /issues/6.json" do
setup do
@parameters = {:issue => {:subject => 'API update', :notes => 'A new note'}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.json',
{:issue => {:subject => 'API update', :notes => 'A new note'}},
{:success_code => :ok})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "create a new journal" do
assert_difference('Journal.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "add the note to the journal" do
put '/issues/6.json', @parameters, @headers
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
put '/issues/6.json', @parameters, @headers
issue = Issue.find(6)
assert_equal "API update", issue.subject
end
end
context "PUT /issues/6.json with failed update" do
setup do
@parameters = {:issue => {:subject => ''}}
@headers = { :authorization => credentials('jsmith') }
end
should_allow_api_authentication(:put,
'/issues/6.json',
{:issue => {:subject => ''}}, # Missing subject should fail
{:success_code => :unprocessable_entity})
should "not create a new issue" do
assert_no_difference('Issue.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "not create a new journal" do
assert_no_difference('Journal.count') do
put '/issues/6.json', @parameters, @headers
end
end
should "have an errors attribute" do
put '/issues/6.json', @parameters, @headers
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "DELETE /issues/1.xml" do
should_allow_api_authentication(:delete,
'/issues/6.xml',
{},
{:success_code => :ok})
should "delete the issue" do
assert_difference('Issue.count',-1) do
delete '/issues/6.xml', {}, :authorization => credentials('jsmith')
end
assert_nil Issue.find_by_id(6)
end
end
context "DELETE /issues/1.json" do
should_allow_api_authentication(:delete,
'/issues/6.json',
{},
{:success_code => :ok})
should "delete the issue" do
assert_difference('Issue.count',-1) do
delete '/issues/6.json', {}, :authorization => credentials('jsmith')
end
assert_nil Issue.find_by_id(6)
end
end
def credentials(user, password=nil)
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
end
end

View File

@@ -15,9 +15,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../test_helper"
require "#{File.dirname(__FILE__)}/../../test_helper"
class ProjectsApiTest < ActionController::IntegrationTest
class ApiTest::ProjectsTest < ActionController::IntegrationTest
fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
:attachments, :custom_fields, :custom_values, :time_entries
@@ -31,23 +31,37 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_response :success
assert_equal 'application/xml', @response.content_type
end
context "GET /projects/2.xml" do
# TODO: A private project is needed because should_allow_api_authentication
# actually tests that authentication is *required*, not just allowed
should_allow_api_authentication(:get, "/projects/2.xml")
end
def test_show
get '/projects/1.xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_create
attributes = {:name => 'API test', :identifier => 'api-test'}
assert_difference 'Project.count' do
post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
end
assert_response :created
assert_equal 'application/xml', @response.content_type
project = Project.first(:order => 'id DESC')
attributes.each do |attribute, value|
assert_equal value, project.send(attribute)
context "POST /projects.xml" do
should_allow_api_authentication(:post,
'/projects.xml',
{:project => {:name => 'API test', :identifier => 'api-test'}},
{:success_code => :created})
should "create a project with the attributes" do
assert_difference('Project.count') do
post '/projects.xml', {:project => {:name => 'API test', :identifier => 'api-test'}}, :authorization => credentials('admin')
end
project = Project.first(:order => 'id DESC')
assert_equal 'API test', project.name
assert_equal 'api-test', project.identifier
assert_response :created
assert_equal 'application/xml', @response.content_type
assert_tag 'project', :child => {:tag => 'id', :content => project.id.to_s}
end
end
@@ -61,16 +75,20 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
end
def test_update
attributes = {:name => 'API update'}
assert_no_difference 'Project.count' do
put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
end
assert_response :ok
assert_equal 'application/xml', @response.content_type
project = Project.find(1)
attributes.each do |attribute, value|
assert_equal value, project.send(attribute)
context "PUT /projects/2.xml" do
should_allow_api_authentication(:put,
'/projects/2.xml',
{:project => {:name => 'API test'}},
{:success_code => :ok})
should "update the project" do
assert_no_difference 'Project.count' do
put '/projects/2.xml', {:project => {:name => 'API update'}}, :authorization => credentials('jsmith')
end
assert_response :ok
assert_equal 'application/xml', @response.content_type
project = Project.find(2)
assert_equal 'API update', project.name
end
end
@@ -83,14 +101,20 @@ class ProjectsApiTest < ActionController::IntegrationTest
assert_equal 'application/xml', @response.content_type
assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
end
def test_destroy
assert_difference 'Project.count', -1 do
delete '/projects/2.xml', {}, :authorization => credentials('admin')
context "DELETE /projects/2.xml" do
should_allow_api_authentication(:delete,
'/projects/2.xml',
{},
{:success_code => :ok})
should "delete the project" do
assert_difference('Project.count',-1) do
delete '/projects/2.xml', {}, :authorization => credentials('admin')
end
assert_response :ok
assert_nil Project.find_by_id(2)
end
assert_response :ok
assert_equal 'application/xml', @response.content_type
assert_nil Project.find_by_id(2)
end
def credentials(user, password=nil)

View File

@@ -0,0 +1,26 @@
require "#{File.dirname(__FILE__)}/../../test_helper"
class ApiTest::TokenAuthenticationTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
should_allow_key_based_auth(:get, "/news.xml")
end
context "in :json format" do
should_allow_key_based_auth(:get, "/news.json")
end
end
end

View File

@@ -1,80 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class ApiTokenLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
get "/news.xml?key=#{@token.value}"
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
get "/news.xml?key=#{@token.value}"
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "in :json format" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
get "/news.json?key=#{@token.value}"
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
get "/news.json?key=#{@token.value}"
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
end

View File

@@ -1,103 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class HttpBasicLoginTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
context "without credentials" do
setup do
get "/projects/onlinestore/news.xml"
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
context "in :json format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "without credentials" do
setup do
get "/projects/onlinestore/news.json"
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
end

View File

@@ -1,84 +0,0 @@
require "#{File.dirname(__FILE__)}/../test_helper"
class HttpBasicLoginWithApiTokenTest < ActionController::IntegrationTest
fixtures :all
def setup
Setting.rest_api_enabled = '1'
Setting.login_required = '1'
end
def teardown
Setting.rest_api_enabled = '0'
Setting.login_required = '0'
end
# Using the NewsController because it's a simple API.
context "get /news" do
context "in :xml format" do
context "with a valid HTTP authentication using the API token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :xml
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
get "/news.xml", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :xml
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
context "in :json format" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'DoesNotMatter')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :success
should_respond_with_content_type :json
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'DoesNotMatter')
get "/news.json", nil, :authorization => @authorization
end
should_respond_with :unauthorized
should_respond_with_content_type :json
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
end

View File

@@ -1,349 +0,0 @@
# Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require "#{File.dirname(__FILE__)}/../test_helper"
class IssuesApiTest < ActionController::IntegrationTest
fixtures :projects,
:users,
:roles,
:members,
:member_roles,
:issues,
:issue_statuses,
:versions,
:trackers,
:projects_trackers,
:issue_categories,
:enabled_modules,
:enumerations,
:attachments,
:workflows,
:custom_fields,
:custom_values,
:custom_fields_projects,
:custom_fields_trackers,
:time_entries,
:journals,
:journal_details,
:queries
def setup
Setting.rest_api_enabled = '1'
end
context "/index.xml" do
setup do
get '/issues.xml'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
end
context "/index.json" do
setup do
get '/issues.json'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
end
context "/index.xml with filter" do
setup do
get '/issues.xml?status_id=5'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
should "show only issues with the status_id" do
assert_tag :tag => 'issues',
:children => { :count => Issue.visible.count(:conditions => {:status_id => 5}),
:only => { :tag => 'issue' } }
end
end
context "/index.json with filter" do
setup do
get '/issues.json?status_id=5'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
should "show only issues with the status_id" do
json = ActiveSupport::JSON.decode(response.body)
status_ids_used = json.collect {|j| j['status_id'] }
assert_equal 3, status_ids_used.length
assert status_ids_used.all? {|id| id == 5 }
end
end
context "/issues/1.xml" do
setup do
get '/issues/1.xml'
end
should_respond_with :success
should_respond_with_content_type 'application/xml'
end
context "/issues/1.json" do
setup do
get '/issues/1.json'
end
should_respond_with :success
should_respond_with_content_type 'application/json'
should 'return a valid JSON string' do
assert ActiveSupport::JSON.decode(response.body)
end
end
context "POST /issues.xml" do
setup do
@issue_count = Issue.count
@attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :created
should_respond_with_content_type 'application/xml'
should "create an issue with the attributes" do
assert_equal Issue.count, @issue_count + 1
issue = Issue.first(:order => 'id DESC')
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute)
end
end
end
context "POST /issues.xml with failure" do
setup do
@attributes = {:project_id => 1}
post '/issues.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/xml'
should "have an errors tag" do
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "POST /issues.json" do
setup do
@issue_count = Issue.count
@attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}
post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :created
should_respond_with_content_type 'application/json'
should "create an issue with the attributes" do
assert_equal Issue.count, @issue_count + 1
issue = Issue.first(:order => 'id DESC')
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute)
end
end
end
context "POST /issues.json with failure" do
setup do
@attributes = {:project_id => 1}
post '/issues.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/json'
should "have an errors element" do
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "PUT /issues/1.xml" do
setup do
@issue_count = Issue.count
@journal_count = Journal.count
@attributes = {:subject => 'API update', :notes => 'A new note'}
put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/xml'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "create a new journal" do
assert_equal Journal.count, @journal_count + 1
end
should "add the note to the journal" do
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
issue = Issue.find(1)
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute) unless attribute == :notes
end
end
end
context "PUT /issues/1.xml with failed update" do
setup do
@attributes = {:subject => ''}
@issue_count = Issue.count
@journal_count = Journal.count
put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/xml'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "not create a new journal" do
assert_equal Journal.count, @journal_count
end
should "have an errors tag" do
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"}
end
end
context "PUT /issues/1.json" do
setup do
@issue_count = Issue.count
@journal_count = Journal.count
@attributes = {:subject => 'API update', :notes => 'A new note'}
put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/json'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "create a new journal" do
assert_equal Journal.count, @journal_count + 1
end
should "add the note to the journal" do
journal = Journal.last
assert_equal "A new note", journal.notes
end
should "update the issue" do
issue = Issue.find(1)
@attributes.each do |attribute, value|
assert_equal value, issue.send(attribute) unless attribute == :notes
end
end
end
context "PUT /issues/1.json with failed update" do
setup do
@attributes = {:subject => ''}
@issue_count = Issue.count
@journal_count = Journal.count
put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith')
end
should_respond_with :unprocessable_entity
should_respond_with_content_type 'application/json'
should "not create a new issue" do
assert_equal Issue.count, @issue_count
end
should "not create a new journal" do
assert_equal Journal.count, @journal_count
end
should "have an errors attribute" do
json = ActiveSupport::JSON.decode(response.body)
assert_equal "can't be blank", json.first['subject']
end
end
context "DELETE /issues/1.xml" do
setup do
@issue_count = Issue.count
delete '/issues/1.xml', {}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/xml'
should "delete the issue" do
assert_equal Issue.count, @issue_count -1
assert_nil Issue.find_by_id(1)
end
end
context "DELETE /issues/1.json" do
setup do
@issue_count = Issue.count
delete '/issues/1.json', {}, :authorization => credentials('jsmith')
end
should_respond_with :ok
should_respond_with_content_type 'application/json'
should "delete the issue" do
assert_equal Issue.count, @issue_count -1
assert_nil Issue.find_by_id(1)
end
end
def credentials(user, password=nil)
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
end
end

View File

@@ -181,4 +181,236 @@ class ActiveSupport::TestCase
assert !user.new_record?
end
end
# Test that a request allows the three types of API authentication
#
# * HTTP Basic with username and password
# * HTTP Basic with an api key for the username
# * Key based with the key=X parameter
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
should_allow_key_based_auth(http_method, url, parameters, options)
end
# Test that a request allows the username and password for HTTP BASIC
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow http basic auth using a username and password for #{http_method} #{url}" do
context "with a valid HTTP authentication" do
setup do
@user = User.generate_with_protected!(:password => 'my_password', :password_confirmation => 'my_password', :admin => true) # Admin so they can access the project
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'my_password')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@user.login, 'wrong_password')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
context "without credentials" do
setup do
send(http_method, url, parameters, {:authorization => ''})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "include_www_authenticate_header" do
assert @controller.response.headers.has_key?('WWW-Authenticate')
end
end
end
end
# Test that a request allows the API key with HTTP BASIC
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow http basic auth with a key for #{http_method} #{url}" do
context "with a valid HTTP authentication using the API token" do
setup do
@user = User.generate_with_protected!(:admin => true)
@token = Token.generate!(:user => @user, :action => 'api')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should_be_a_valid_response_string_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid HTTP authentication" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
@authorization = ActionController::HttpAuthentication::Basic.encode_credentials(@token.value, 'X')
send(http_method, url, parameters, {:authorization => @authorization})
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
# Test that a request allows full key authentication
#
# @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
# @param [String] url the request url, without the key=ZXY parameter
# @param [optional, Hash] parameters additional request parameters
# @param [optional, Hash] options additional options
# @option options [Symbol] :success_code Successful response code (:success)
# @option options [Symbol] :failure_code Failure response code (:unauthorized)
def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
success_code = options[:success_code] || :success
failure_code = options[:failure_code] || :unauthorized
context "should allow key based auth using key=X for #{http_method} #{url}" do
context "with a valid api token" do
setup do
@user = User.generate_with_protected!(:admin => true)
@token = Token.generate!(:user => @user, :action => 'api')
# Simple url parse to add on ?key= or &key=
request_url = if url.match(/\?/)
url + "&key=#{@token.value}"
else
url + "?key=#{@token.value}"
end
send(http_method, request_url, parameters)
end
should_respond_with success_code
should_respond_with_content_type_based_on_url(url)
should_be_a_valid_response_string_based_on_url(url)
should "login as the user" do
assert_equal @user, User.current
end
end
context "with an invalid api token" do
setup do
@user = User.generate_with_protected!
@token = Token.generate!(:user => @user, :action => 'feeds')
# Simple url parse to add on ?key= or &key=
request_url = if url.match(/\?/)
url + "&key=#{@token.value}"
else
url + "?key=#{@token.value}"
end
send(http_method, request_url, parameters)
end
should_respond_with failure_code
should_respond_with_content_type_based_on_url(url)
should "not login as the user" do
assert_equal User.anonymous, User.current
end
end
end
end
# Uses should_respond_with_content_type based on what's in the url:
#
# '/project/issues.xml' => should_respond_with_content_type :xml
# '/project/issues.json' => should_respond_with_content_type :json
#
# @param [String] url Request
def self.should_respond_with_content_type_based_on_url(url)
case
when url.match(/xml/i)
should_respond_with_content_type :xml
when url.match(/json/i)
should_respond_with_content_type :json
else
raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
end
end
# Uses the url to assert which format the response should be in
#
# '/project/issues.xml' => should_be_a_valid_xml_string
# '/project/issues.json' => should_be_a_valid_json_string
#
# @param [String] url Request
def self.should_be_a_valid_response_string_based_on_url(url)
case
when url.match(/xml/i)
should_be_a_valid_xml_string
when url.match(/json/i)
should_be_a_valid_json_string
else
raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
end
end
# Checks that the response is a valid JSON string
def self.should_be_a_valid_json_string
should "be a valid JSON string (or empty)" do
assert (response.body.blank? || ActiveSupport::JSON.decode(response.body))
end
end
# Checks that the response is a valid XML string
def self.should_be_a_valid_xml_string
should "be a valid XML string" do
assert REXML::Document.new(response.body)
end
end
end
# Simple module to "namespace" all of the API tests
module ApiTest
end

View File

@@ -1,7 +1,7 @@
require File.dirname(__FILE__) + '/../test_helper'
class BoardTest < ActiveSupport::TestCase
fixtures :projects, :boards, :messages
fixtures :projects, :boards, :messages, :attachments, :watchers
def setup
@project = Project.find(1)
@@ -23,8 +23,13 @@ class BoardTest < ActiveSupport::TestCase
def test_destroy
board = Board.find(1)
assert board.destroy
# make sure that the associated messages are removed
assert_difference 'Message.count', -6 do
assert_difference 'Attachment.count', -1 do
assert_difference 'Watcher.count', -1 do
assert board.destroy
end
end
end
assert_equal 0, Message.count(:conditions => {:board_id => 1})
end
end

View File

@@ -11,12 +11,15 @@ begin
REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
def test_hgversion
to_test = { "0.9.5" => [0,9,5],
"1.0" => [1,0],
"1e4ddc9ac9f7+20080325" => nil,
"1.0.1+20080525" => [1,0,1],
"1916e629a29d" => nil}
to_test = { "Mercurial Distributed SCM (version 0.9.5)\n" => [0,9,5],
"Mercurial Distributed SCM (1.0)\n" => [1,0],
"Mercurial Distributed SCM (1e4ddc9ac9f7+20080325)\n" => nil,
"Mercurial Distributed SCM (1.0.1+20080525)\n" => [1,0,1],
"Mercurial Distributed SCM (1916e629a29d)\n" => nil,
"Mercurial SCM Distribuito (versione 0.9.5)\n" => [0,9,5],
"(1.6)\n(1.7)\n(1.8)" => [1,6],
"(1.7.1)\r\n(1.8.1)\r\n(1.9.1)" => [1,7,1]}
to_test.each do |s, v|
test_hgversion_for(s, v)
end
@@ -26,8 +29,9 @@ begin
to_test = { [0,9,5] => "0.9.5",
[1,0] => "1.0",
[] => "1.0",
[1,0,1] => "1.0"}
[1,0,1] => "1.0",
[1,7] => "1.0",
[1,7,1] => "1.0"}
to_test.each do |v, template|
test_template_path_for(v, template)
end
@@ -49,5 +53,8 @@ begin
end
rescue LoadError
def test_fake; assert(false, "Requires mocha to run those tests") end
class MercurialMochaFake < ActiveSupport::TestCase
def test_fake; assert(false, "Requires mocha to run those tests") end
end
end