GitLab steht wegen Wartungsarbeiten am Montag, den 10. Mai, zwischen 17:00 und 19:00 Uhr nicht zur Verfügung.

issues_controller_test.rb 212 KB
Newer Older
1 2
# frozen_string_literal: false

3
# Redmine - project management software
jplang's avatar
jplang committed
4
# Copyright (C) 2006-2017  Jean-Philippe Lang
5 6 7 8 9
#
# 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.
10
#
11 12 13 14
# 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.
15
#
16 17 18 19
# 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.

20
require File.expand_path('../../test_helper', __FILE__)
21

22
class IssuesControllerTest < Redmine::ControllerTest
23
  fixtures :projects,
24
           :users, :email_addresses, :user_preferences,
25 26
           :roles,
           :members,
27
           :member_roles,
28 29
           :issues,
           :issue_statuses,
30
           :issue_relations,
31
           :versions,
32
           :trackers,
33
           :projects_trackers,
34 35 36
           :issue_categories,
           :enabled_modules,
           :enumerations,
37
           :attachments,
38 39 40
           :workflows,
           :custom_fields,
           :custom_values,
jplang's avatar
jplang committed
41
           :custom_fields_projects,
42
           :custom_fields_trackers,
43 44
           :time_entries,
           :journals,
edavis10's avatar
edavis10 committed
45
           :journal_details,
46 47
           :queries,
           :repositories,
jplang's avatar
jplang committed
48 49
           :changesets,
           :watchers
50

51 52
  include Redmine::I18n

53 54 55
  def setup
    User.current = nil
  end
56

57
  def test_index
58 59 60
    with_settings :default_language => "en" do
      get :index
      assert_response :success
61 62

      # links to visible issues
63
      assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
64
      assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
65
      # private projects hidden
66 67
      assert_select 'a[href="/issues/6"]', 0
      assert_select 'a[href="/issues/4"]', 0
68
      # project column
69
      assert_select 'th', :text => /Project/
70
    end
71
  end
72

73
  def test_index_should_not_list_issues_when_module_disabled
jplang's avatar
jplang committed
74
    EnabledModule.where("name = 'issue_tracking' AND project_id = 1").delete_all
75 76
    get :index
    assert_response :success
77

78 79
    assert_select 'a[href="/issues/1"]', 0
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
80
  end
jplang's avatar
jplang committed
81 82

  def test_index_should_list_visible_issues_only
83 84 85
    get :index, :params => {
        :per_page => 100
      }
jplang's avatar
jplang committed
86
    assert_response :success
87 88 89 90

    Issue.open.each do |issue|
      assert_select "tr#issue-#{issue.id}", issue.visible? ? 1 : 0
    end
jplang's avatar
jplang committed
91
  end
92

93
  def test_index_with_project
94
    Setting.display_subprojects_issues = 0
95 96 97
    get :index, :params => {
        :project_id => 1
      }
98
    assert_response :success
99

100 101 102 103 104 105 106 107 108 109 110
    # query form
    assert_select 'form#query_form' do
      assert_select 'div#query_form_with_buttons.hide-when-print' do
        assert_select 'div#query_form_content' do
          assert_select 'fieldset#filters.collapsible'
          assert_select 'fieldset#options'
        end
        assert_select 'p.buttons'
      end
    end

111
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
112
    assert_select 'a[href="/issues/5"]', 0
113
  end
114

115 116
  def test_index_with_project_and_subprojects
    Setting.display_subprojects_issues = 1
117 118 119
    get :index, :params => {
        :project_id => 1
      }
120
    assert_response :success
121

122
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
123 124
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
    assert_select 'a[href="/issues/6"]', 0
125
  end
126

127
  def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
128 129
    @request.session[:user_id] = 2
    Setting.display_subprojects_issues = 1
130 131 132
    get :index, :params => {
        :project_id => 1
      }
133
    assert_response :success
134

135
    assert_select 'a[href="/issues/1"]', :text => /Cannot print recipes/
136 137
    assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
    assert_select 'a[href="/issues/6"]', :text => /Issue of a private subproject/
138
  end
139

140
  def test_index_with_project_and_default_filter
141 142 143 144
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1
      }
145
    assert_response :success
146

147
    # default filter
148
    assert_query_filters [['status_id', 'o', '']]
149
  end
150

151
  def test_index_with_project_and_filter
152 153 154 155 156 157
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :f => ['tracker_id'],
        :op => {
          'tracker_id' => '='
158
        },
159 160 161 162
        :v => {
          'tracker_id' => ['1']
        }
      }
163
    assert_response :success
164

165
    assert_query_filters [['tracker_id', '=', '1']]
166
  end
167

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
  def test_index_with_short_filters
    to_test = {
      'status_id' => {
        'o' => { :op => 'o', :values => [''] },
        'c' => { :op => 'c', :values => [''] },
        '7' => { :op => '=', :values => ['7'] },
        '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
        '=7' => { :op => '=', :values => ['7'] },
        '!3' => { :op => '!', :values => ['3'] },
        '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
      'subject' => {
        'This is a subject' => { :op => '=', :values => ['This is a subject'] },
        'o' => { :op => '=', :values => ['o'] },
        '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
        '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
183 184
      'tracker_id' => {
        '3' => { :op => '=', :values => ['3'] },
emassip's avatar
emassip committed
185
        '=3' => { :op => '=', :values => ['3'] }},
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
      'start_date' => {
        '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
        '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
        '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
        '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
        '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
        '<t+2' => { :op => '<t+', :values => ['2'] },
        '>t+2' => { :op => '>t+', :values => ['2'] },
        't+2' => { :op => 't+', :values => ['2'] },
        't' => { :op => 't', :values => [''] },
        'w' => { :op => 'w', :values => [''] },
        '>t-2' => { :op => '>t-', :values => ['2'] },
        '<t-2' => { :op => '<t-', :values => ['2'] },
        't-2' => { :op => 't-', :values => ['2'] }},
      'created_on' => {
        '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
202 203 204
        '<t-2' => { :op => '<t-', :values => ['2'] },
        '>t-2' => { :op => '>t-', :values => ['2'] },
        't-2' => { :op => 't-', :values => ['2'] }},
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
      'cf_1' => {
        'c' => { :op => '=', :values => ['c'] },
        '!c' => { :op => '!', :values => ['c'] },
        '!*' => { :op => '!*', :values => [''] },
        '*' => { :op => '*', :values => [''] }},
      'estimated_hours' => {
        '=13.4' => { :op => '=', :values => ['13.4'] },
        '>=45' => { :op => '>=', :values => ['45'] },
        '<=125' => { :op => '<=', :values => ['125'] },
        '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
        '!*' => { :op => '!*', :values => [''] },
        '*' => { :op => '*', :values => [''] }}
    }

    default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}

    to_test.each do |field, expression_and_expected|
      expression_and_expected.each do |filter_expression, expected|
emassip's avatar
emassip committed
223

224 225 226
        get :index, :params => {
            :set_filter => 1, field => filter_expression
          }
227 228
        assert_response :success

229 230
        expected_with_default = default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}})
        assert_query_filters expected_with_default.map {|f, v| [f, v[:operator], v[:values]]}
231 232 233 234
      end
    end
  end

235
  def test_index_with_project_and_empty_filters
236 237 238 239 240
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :fields => ['']
      }
241
    assert_response :success
242

243
    # no filter
244
    assert_query_filters []
245
  end
246

247 248 249 250 251 252 253
  def test_index_with_project_custom_field_filter
    field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
    CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
    CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
    filter_name = "project.cf_#{field.id}"
    @request.session[:user_id] = 1

254 255 256 257 258
    get :index, :params => {
        :set_filter => 1,
        :f => [filter_name],
        :op => {
          filter_name => '='
259
        },
260 261
        :v => {
          filter_name => ['Foo']
262
        },
263 264
        :c => ['project']
      }
265
    assert_response :success
266 267

    assert_equal [3, 5], issues_in_list.map(&:project_id).uniq.sort
268 269
  end

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
  def test_index_with_project_status_filter
    project = Project.find(2)
    project.close
    project.save

    get :index, :params => {
        :set_filter => 1,
        :f => ['project.status'],
        :op => {'project.status' => '='},
        :v => {'project.status' => ['1']}
      }

    assert_response :success

    issues = issues_in_list.map(&:id).uniq.sort
    assert_include 1, issues
    assert_not_include 4, issues
  end

jplang's avatar
jplang committed
289
  def test_index_with_query
290 291 292 293
    get :index, :params => {
        :project_id => 1,
        :query_id => 5
      }
jplang's avatar
jplang committed
294 295
    assert_response :success
  end
296

297
  def test_index_with_query_grouped_by_tracker
298 299 300 301
    get :index, :params => {
        :project_id => 1,
        :query_id => 6
      }
jplang's avatar
jplang committed
302
    assert_response :success
303
    assert_select 'tr.group span.count'
304
  end
305

306
  def test_index_with_query_grouped_and_sorted_by_category
307 308 309 310 311 312
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "category",
        :sort => "category"
      }
313
    assert_response :success
314
    assert_select 'tr.group span.count'
315 316
  end

317
  def test_index_with_query_grouped_and_sorted_by_fixed_version
318 319 320 321 322 323
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "fixed_version",
        :sort => "fixed_version"
      }
324
    assert_response :success
325
    assert_select 'tr.group span.count'
326 327 328
  end

  def test_index_with_query_grouped_and_sorted_by_fixed_version_in_reverse_order
329 330 331 332 333 334
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
        :group_by => "fixed_version",
        :sort => "fixed_version:desc"
      }
335
    assert_response :success
336
    assert_select 'tr.group span.count'
337 338
  end

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
  def test_index_grouped_by_due_date
    Issue.destroy_all
    Issue.generate!(:due_date => '2018-08-10')
    Issue.generate!(:due_date => '2018-08-10')
    Issue.generate!

    get :index, :params => {
        :set_filter => 1,
        :group_by => "due_date"
      }
    assert_response :success
    assert_select 'tr.group span.name', :value => '2018-08-10' do
      assert_select '~ span.count', value:'2'
    end
    assert_select 'tr.group span.name', :value => '(blank)' do
      assert_select '~ span.count', value:'1'
    end
  end

358 359
  def test_index_grouped_by_created_on
    skip unless IssueQuery.new.groupable_columns.detect {|c| c.name == :created_on}
360

361 362 363 364 365
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'created_on'
      }
    assert_response :success
366

367 368 369 370
    assert_select 'tr.group span.name', :text => '07/19/2006' do
      assert_select '+ span.count', :text => '2'
    end
  end
371

372 373
  def test_index_grouped_by_created_on_as_pdf
    skip unless IssueQuery.new.groupable_columns.detect {|c| c.name == :created_on}
374

375 376 377 378 379 380 381 382 383
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'created_on',
        :format => 'pdf'
      }
    assert_response :success
    assert_equal 'application/pdf', response.content_type
  end

384
  def test_index_with_query_grouped_by_list_custom_field
385 386 387 388
    get :index, :params => {
        :project_id => 1,
        :query_id => 9
      }
389
    assert_response :success
390
    assert_select 'tr.group span.count'
jplang's avatar
jplang committed
391
  end
392

393 394 395 396 397 398 399 400 401
  def test_index_with_query_grouped_by_key_value_custom_field
    cf = IssueCustomField.create!(:name => 'Key', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'enumeration')
    cf.enumerations << valueb = CustomFieldEnumeration.new(:name => 'Value B', :position => 1)
    cf.enumerations << valuea = CustomFieldEnumeration.new(:name => 'Value A', :position => 2)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => valueb.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => valueb.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => valuea.id)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')

402 403 404 405 406
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
      :group_by => "cf_#{cf.id}"
      }
407 408 409 410 411 412 413 414 415 416 417 418 419
    assert_response :success

    assert_select 'tr.group', 3
    assert_select 'tr.group' do
      assert_select 'span.name', :text => 'Value B'
      assert_select 'span.count', :text => '2'
    end
    assert_select 'tr.group' do
      assert_select 'span.name', :text => 'Value A'
      assert_select 'span.count', :text => '1'
    end
  end

420 421 422 423 424 425 426
  def test_index_with_query_grouped_by_user_custom_field
    cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')

427 428 429 430 431
    get :index, :params => {
        :project_id => 1,
        :set_filter => 1,
      :group_by => "cf_#{cf.id}"
      }
432 433 434 435 436
    assert_response :success

    assert_select 'tr.group', 3
    assert_select 'tr.group' do
      assert_select 'a', :text => 'John Smith'
jplang's avatar
jplang committed
437
      assert_select 'span.count', :text => '1'
438 439 440
    end
    assert_select 'tr.group' do
      assert_select 'a', :text => 'Dave Lopper'
jplang's avatar
jplang committed
441
      assert_select 'span.count', :text => '2'
442 443 444
    end
  end

445 446 447 448 449 450 451
  def test_index_grouped_by_boolean_custom_field_should_distinguish_blank_and_false_values
    cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '1')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '')

    with_settings :default_language => 'en' do
452 453 454 455 456
      get :index, :params => {
          :project_id => 1,
          :set_filter => 1,
        :group_by => "cf_#{cf.id}"
        }
457 458 459
      assert_response :success
    end

460
    assert_select 'tr.group', 3
461 462
    assert_select 'tr.group', :text => /Yes/
    assert_select 'tr.group', :text => /No/
jplang's avatar
jplang committed
463
    assert_select 'tr.group', :text => /blank/
464 465
  end

466 467 468 469 470 471
  def test_index_grouped_by_boolean_custom_field_with_false_group_in_first_position_should_show_the_group
    cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool', :is_filter => true)
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '0')
    CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')

    with_settings :default_language => 'en' do
472 473 474 475 476
      get :index, :params => {
          :project_id => 1,
        :set_filter => 1, "cf_#{cf.id}" => "*",
        :group_by => "cf_#{cf.id}"
        }
477 478 479
      assert_response :success
    end

480
    assert_equal [1, 2], issues_in_list.map(&:id).sort
481 482 483 484
    assert_select 'tr.group', 1
    assert_select 'tr.group', :text => /No/
  end

485
  def test_index_with_query_grouped_by_tracker_in_normal_order
486 487
    3.times {|i| Issue.generate!(:tracker_id => (i + 1))}

488 489 490 491 492
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'tracker',
        :sort => 'id:desc'
      }
493 494
    assert_response :success

495 496
    assert_equal ["Bug", "Feature request", "Support request"],
      css_select("tr.issue td.tracker").map(&:text).uniq
497 498 499 500 501
  end

  def test_index_with_query_grouped_by_tracker_in_reverse_order
    3.times {|i| Issue.generate!(:tracker_id => (i + 1))}

502 503 504 505 506 507
    get :index, :params => {
        :set_filter => 1,
        :group_by => 'tracker',
        :c => ['tracker', 'subject'],
        :sort => 'id:desc,tracker:desc'
      }
508 509
    assert_response :success

510 511
    assert_equal ["Bug", "Feature request", "Support request"].reverse,
      css_select("tr.issue td.tracker").map(&:text).uniq
512 513
  end

514
  def test_index_with_query_id_and_project_id_should_set_session_query
515 516 517 518
    get :index, :params => {
        :project_id => 1,
        :query_id => 4
      }
519
    assert_response :success
520 521 522
    assert_kind_of Hash, session[:issue_query]
    assert_equal 4, session[:issue_query][:id]
    assert_equal 1, session[:issue_query][:project_id]
523 524
  end

525
  def test_index_with_invalid_query_id_should_respond_404
526 527 528 529
    get :index, :params => {
        :project_id => 1,
        :query_id => 999
      }
530 531 532
    assert_response 404
  end

533
  def test_index_with_cross_project_query_in_session_should_show_project_issues
534
    q = IssueQuery.create!(:name => "cross_project_query", :user_id => 2, :project => nil, :column_names => ['project'])
535
    @request.session[:issue_query] = {:id => q.id, :project_id => 1}
536 537

    with_settings :display_subprojects_issues => '0' do
538 539 540
      get :index, :params => {
          :project_id => 1
        }
541 542
    end
    assert_response :success
543 544 545

    assert_select 'h2', :text => q.name
    assert_equal ["eCookbook"], css_select("tr.issue td.project").map(&:text).uniq
546 547
  end

548
  def test_private_query_should_not_be_available_to_other_users
jplang's avatar
jplang committed
549
    q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
550
    @request.session[:user_id] = 3
551

552 553 554
    get :index, :params => {
        :query_id => q.id
      }
555 556
    assert_response 403
  end
557

558
  def test_private_query_should_be_available_to_its_user
jplang's avatar
jplang committed
559
    q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
560
    @request.session[:user_id] = 2
561

562 563 564
    get :index, :params => {
        :query_id => q.id
      }
565 566
    assert_response :success
  end
567

568
  def test_public_query_should_be_available_to_other_users
569
    q = IssueQuery.create!(:name => "public", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
570
    @request.session[:user_id] = 3
571

572 573 574
    get :index, :params => {
        :query_id => q.id
      }
575 576
    assert_response :success
  end
577

578
  def test_index_should_omit_page_param_in_export_links
579 580 581
    get :index, :params => {
        :page => 2
      }
582
    assert_response :success
583 584 585
    assert_select 'a.atom[href="/issues.atom"]'
    assert_select 'a.csv[href="/issues.csv"]'
    assert_select 'a.pdf[href="/issues.pdf"]'
jplang's avatar
jplang committed
586
    assert_select 'form#csv-export-form[action="/issues.csv"]'
587 588
  end

589 590 591 592 593 594 595 596 597 598 599 600 601 602
  def test_index_should_not_warn_when_not_exceeding_export_limit
    with_settings :issues_export_limit => 200 do
      get :index
      assert_select '#csv-export-options p.icon-warning', 0
    end
  end

  def test_index_should_warn_when_exceeding_export_limit
    with_settings :issues_export_limit => 2 do
      get :index
      assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
    end
  end

603
  def test_index_should_include_query_params_as_hidden_fields_in_csv_export_form
604 605 606 607 608 609 610
    get :index, :params => {
        :project_id => 1,
        :set_filter => "1",
        :tracker_id => "2",
        :sort => 'status',
        :c => ["status", "priority"]
      }
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626

    assert_select '#csv-export-form[action=?]', '/projects/ecookbook/issues.csv'
    assert_select '#csv-export-form[method=?]', 'get'

    assert_select '#csv-export-form' do
      assert_select 'input[name=?][value=?]', 'set_filter', '1'

      assert_select 'input[name=?][value=?]', 'f[]', 'tracker_id'
      assert_select 'input[name=?][value=?]', 'op[tracker_id]', '='
      assert_select 'input[name=?][value=?]', 'v[tracker_id][]', '2'

      assert_select 'input[name=?][value=?]', 'c[]', 'status'
      assert_select 'input[name=?][value=?]', 'c[]', 'priority'

      assert_select 'input[name=?][value=?]', 'sort', 'status'
    end
627

628 629 630 631 632
    get :index, :params => {
        :project_id => 1,
        :set_filter => "1",
        :f => ['']
      }
633
    assert_select '#csv-export-form input[name=?][value=?]', 'f[]', ''
634 635
  end

636
  def test_index_csv
637 638 639
    get :index, :params => {
        :format => 'csv'
      }
640
    assert_response :success
641

jplang's avatar
jplang committed
642
    assert_equal 'text/csv; header=present', @response.content_type
643 644 645 646
    assert response.body.starts_with?("#,")
    lines = response.body.chomp.split("\n")
    # default columns + id and project
    assert_equal Setting.issue_list_default_columns.size + 2, lines[0].split(',').size
647
  end
648

649
  def test_index_csv_with_project
650 651 652 653
    get :index, :params => {
        :project_id => 1,
        :format => 'csv'
      }
654
    assert_response :success
655

jplang's avatar
jplang committed
656
    assert_equal 'text/csv; header=present', @response.content_type
657
  end
658

659 660 661
  def test_index_csv_without_any_filters
    @request.session[:user_id] = 1
    Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 5, :subject => 'Closed issue', :author_id => 1)
662 663 664 665 666
    get :index, :params => {
        :set_filter => 1,
        :f => [''],
        :format => 'csv'
      }
667
    assert_response :success
668 669
    # -1 for headers
    assert_equal Issue.count, response.body.chomp.split("\n").size - 1
670 671
  end

672
  def test_index_csv_with_description
673 674 675
    Issue.generate!(:description => 'test_index_csv_with_description')

    with_settings :default_language => 'en' do
676 677 678 679 680
      get :index, :params => {
          :format => 'csv',
          :c => [:tracker,
          :description]
        }
681 682 683 684 685 686 687
      assert_response :success
    end

    assert_equal 'text/csv; header=present', response.content_type
    headers = response.body.chomp.split("\n").first.split(',')
    assert_include 'Description', headers
    assert_include 'test_index_csv_with_description', response.body
688 689
  end

690
  def test_index_csv_with_spent_time_column
jplang's avatar
jplang committed
691 692
    issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
    TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
693

694 695 696 697 698
    get :index, :params => {
        :format => 'csv',
        :set_filter => '1',
        :c => %w(subject spent_hours)
      }
699
    assert_response :success
jplang's avatar
jplang committed
700
    assert_equal 'text/csv; header=present', @response.content_type
701 702 703 704
    lines = @response.body.chomp.split("\n")
    assert_include "#{issue.id},#{issue.subject},7.33", lines
  end

705
  def test_index_csv_with_all_columns
706 707 708 709
    get :index, :params => {
        :format => 'csv',
        :c => ['all_inline']
      }
710
    assert_response :success
711

jplang's avatar
jplang committed
712
    assert_equal 'text/csv; header=present', @response.content_type
713 714
    assert_match /\A#,/, response.body
    lines = response.body.chomp.split("\n")
715
    assert_equal IssueQuery.new.available_inline_columns.size, lines[0].split(',').size
716 717
  end

jplang's avatar
jplang committed
718 719 720 721 722 723
  def test_index_csv_with_multi_column_field
    CustomField.find(1).update_attribute :multiple, true
    issue = Issue.find(1)
    issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
    issue.save!

724 725 726 727
    get :index, :params => {
        :format => 'csv',
        :c => ['tracker', "cf_1"]
      }
jplang's avatar
jplang committed
728 729 730 731 732
    assert_response :success
    lines = @response.body.chomp.split("\n")
    assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
  end

733 734 735 736 737
  def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
    field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
    issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})

    with_settings :default_language => 'fr' do
738 739 740 741
      get :index, :params => {
          :format => 'csv',
        :c => ['id', 'tracker', "cf_#{field.id}"]
        }
742 743 744 745 746 747
      assert_response :success
      issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
      assert_include '185,60', issue_line
    end

    with_settings :default_language => 'en' do
748 749 750 751
      get :index, :params => {
          :format => 'csv',
        :c => ['id', 'tracker', "cf_#{field.id}"]
        }
752 753 754 755 756
      assert_response :success
      issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
      assert_include '185.60', issue_line
    end
  end
757 758 759 760 761 762 763

  def test_index_csv_should_fill_parent_column_with_parent_id
    Issue.delete_all
    parent = Issue.generate!
    child = Issue.generate!(:parent_issue_id => parent.id)

    with_settings :default_language => 'en' do
764 765 766 767
      get :index, :params => {
          :format => 'csv',
          :c => %w(parent)
        }
768
    end
jplang's avatar
Typo.  
jplang committed
769
    lines = response.body.split("\n")
770 771
    assert_include "#{child.id},#{parent.id}", lines
  end
772

773 774
  def test_index_csv_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
775 776
      str_utf8  = "\xe4\xb8\x80\xe6\x9c\x88".force_encoding('UTF-8')
      str_big5  = "\xa4@\xa4\xeb".force_encoding('Big5')
jplang's avatar
jplang committed
777
      issue = Issue.generate!(:subject => str_utf8)
778

779 780 781 782 783
      get :index, :params => {
          :project_id => 1,
          :subject => str_utf8,
          :format => 'csv'
        }
jplang's avatar
jplang committed
784
      assert_equal 'text/csv; header=present', @response.content_type
785
      lines = @response.body.chomp.split("\n")
786
      header = lines[0]
787
      status = "\xaa\xac\xbaA".force_encoding('Big5')
788
      assert_include status, header
789
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
790
      assert_include str_big5, issue_line
791 792 793
    end
  end

794 795
  def test_index_csv_cannot_convert_should_be_replaced_big_5
    with_settings :default_language => "zh-TW" do
jplang's avatar
jplang committed
796
      str_utf8  = "\xe4\xbb\xa5\xe5\x86\x85".force_encoding('UTF-8')
jplang's avatar
jplang committed
797
      issue = Issue.generate!(:subject => str_utf8)
798

799 800 801 802 803 804 805
      get :index, :params => {
          :project_id => 1,
          :subject => str_utf8,
          :c => ['status', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
806
      assert_equal 'text/csv; header=present', @response.content_type
807
      lines = @response.body.chomp.split("\n")
808 809
      header = lines[0]
      issue_line = lines.find {|l| l =~ /^#{issue.id},/}
jplang's avatar
jplang committed
810
      s1 = "\xaa\xac\xbaA".force_encoding('Big5') # status
811 812
      assert header.include?(s1)
      s2 = issue_line.split(",")[2]
jplang's avatar
jplang committed
813 814
      s3 = "\xa5H?".force_encoding('Big5') # subject
      assert_equal s3, s2
815 816 817
    end
  end

818 819 820
  def test_index_csv_tw
    with_settings :default_language => "zh-TW" do
      str1  = "test_index_csv_tw"
jplang's avatar
jplang committed
821
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
822

823 824 825 826 827 828 829
      get :index, :params => {
          :project_id => 1,
          :subject => str1,
          :c => ['estimated_hours', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
830
      assert_equal 'text/csv; header=present', @response.content_type
831
      lines = @response.body.chomp.split("\n")
832
      assert_include "#{issue.id},1234.50,#{str1}", lines
833 834 835 836 837 838
    end
  end

  def test_index_csv_fr
    with_settings :default_language => "fr" do
      str1  = "test_index_csv_fr"
jplang's avatar
jplang committed
839
      issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
840

841 842 843 844 845 846 847
      get :index, :params => {
          :project_id => 1,
          :subject => str1,
          :c => ['estimated_hours', 'subject'],
          :format => 'csv',
          :set_filter => 1
        }
jplang's avatar
jplang committed
848
      assert_equal 'text/csv; header=present', @response.content_type
849
      lines = @response.body.chomp.split("\n")
850
      assert_include "#{issue.id};1234,50;#{str1}", lines
851 852 853
    end
  end

854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
  def test_index_csv_should_not_change_selected_columns
    get :index, :params => {
        :set_filter => 1,
        :c => ["subject", "due_date"],
        :project_id => "ecookbook"
      }
    assert_response :success
    assert_equal [:subject, :due_date], session[:issue_query][:column_names]

    get :index, :params => {
        :set_filter => 1,
        :c =>["all_inline"],
        :project_id => "ecookbook",
        :format => 'csv'
      }
    assert_response :success
    assert_equal [:subject, :due_date], session[:issue_query][:column_names]
  end

873
  def test_index_pdf
874 875
    ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
      with_settings :default_language => lang do
876

877 878
        get :index
        assert_response :success
879