require 'rails_helper'

describe 'Issues API' do

  include_context 'project scoped API'
  include_context 'https'
  include_context 'versioned API'

  let(:api_version) { 1 }

  context 'as unauthenticated user' do
    [
      ['get', '/api/issues/'],
      ['get', '/api/issues/1'],
      ['post', '/api/issues/'],
      ['put', '/api/issues/1'],
      ['patch', '/api/issues/1'],
      ['delete', '/api/issues/1'],
    ].each do |verb, url|
      describe '#{verb.upcase} #{url}' do
        it 'throws 401' do
          send(verb, url, params: {}, env: @env)
          expect(response.status).to eq 401
        end
      end
    end
  end

  context 'as authorized user' do
    include_context 'authorized API user'

    describe 'GET /api/issues' do
      before(:each) do
        @issues = create_list(:issue, 30, node: current_project.issue_library).sort_by(&:title)

        get path, env: @env
        expect(response.status).to eq(200)

        @retrieved_issues = JSON.parse(response.body)
      end

      context 'without params' do
        let(:path) { '/api/issues' }

        it 'retrieves all the issues' do
          titles = @issues.map(&:title)
          retrieved_titles = @retrieved_issues.map { |json| json['title'] }

          expect(@retrieved_issues.count).to eq(@issues.count)
          expect(retrieved_titles).to match_array(titles)
        end

        it 'includes fields' do
          @retrieved_issues.each do |issue|
            expect(issue).to have_key('id')
            db_issue = Issue.find(issue['id'])

            expect(issue['fields']).not_to be_empty
            expect(issue['fields'].count).to eq(db_issue.fields.count)
            expect(issue['fields'].keys).to eq(db_issue.fields.keys)
          end
        end
      end

      context 'with params' do
        let(:path) { '/api/issues?page=2' }

        it 'retrieves the paginated issues' do
          expect(@retrieved_issues.count).to eq(5)
        end
      end
    end

    describe 'GET /api/issue/:id' do
      before(:each) do
        @issue = create(:issue, :tagged_issue, node: current_project.issue_library, text: "#[a]#\nb\n\n#[c]#\nd\n\n#[e]#\nf\n\n")

        get "/api/issues/#{ @issue.id }", env: @env
        expect(response.status).to eq(200)

        @retrieved_issue = JSON.parse(response.body)
      end

      it 'retrieves a specific issue' do
        expect(@retrieved_issue['id']).to eq @issue.id
      end

      it 'includes fields' do
        expect(@retrieved_issue['fields']).not_to be_empty
        expect(@retrieved_issue['fields'].keys).to eq @issue.fields.keys
        expect(@retrieved_issue['fields'].count).to eq @issue.fields.count
      end

      it 'includes tags' do
        tag = @issue.tags.first
        expect(@retrieved_issue['tags']).to eq [{ 'color' => tag.color, 'display_name' => tag.display_name }]
      end

      it 'includes the author' do
        expect(@retrieved_issue['author']).to eq @issue.author
      end

      it 'includes the state' do
        expect(@retrieved_issue['state']).to eq @issue.state
      end
    end

    describe 'POST /api/issues' do
      let(:valid_params) do
        { issue: { text: "#[Title]#\nRspec issue\n\n#[c]#\nd\n\n#[e]#\nf\n\n", state: 'ready_for_review' } }
      end
      let(:valid_post) do
        post '/api/issues', params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json')
      end

      it 'creates a new issue' do
        expect { valid_post }.to change { current_project.issues.count }.by(1)
        expect(response.status).to eq(201)
        retrieved_issue = JSON.parse(response.body)
        expect(retrieved_issue['text']).to eq valid_params[:issue][:text]
      end

      it 'tags the issue from the Tags field' do
        tag_name = '!2ca02c_info'
        valid_params[:issue][:text] << "#[Tags]#\n\n#{tag_name}\n\n"

        expect { valid_post }.to change { current_project.issues.count }.by(1)
        expect(response.status).to eq(201)

        retrieved_issue = JSON.parse(response.body)
        database_issue  = current_project.issues.find(retrieved_issue['id'])

        expect(database_issue.tag_list).to eq(tag_name)
      end

      it 'sets the issue state' do
        expect { valid_post }.to change { current_project.issues.count }.by(1)
        expect(response.status).to eq(201)

        retrieved_issue = JSON.parse(response.body)
        expect(retrieved_issue['state']).to eq('ready_for_review')
      end

      let(:submit_form) { valid_post }
      include_examples 'creates an Activity', :create, Issue
      include_examples 'sets the whodunnit', :create, Issue

      it 'throws 415 unless JSON is sent' do
        params = { issue: { name: 'Bad Issue' } }
        post '/api/issues', params: params, env: @env
        expect(response.status).to eq(415)
      end

      it 'throws 422 if issue is invalid' do
        params = { issue: { text: 'A' * (65535 + 1) } }
        expect {
          post '/api/issues', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json')
        }.not_to change { current_project.issues.count }
        expect(response.status).to eq(422)
      end

      it 'throws 422 if state is invalid' do
        params = { issue: { text: "#[Title]#\nIssue test\n", state: 'fakestate' } }
        expect {
          post '/api/issues', params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json')
        }.not_to change { current_project.issues.count }
        expect(response.status).to eq(422)
      end
    end

    describe 'PUT /api/issues/:id' do
      let(:issue) { create(:issue, node: current_project.issue_library, text: 'Existing Issue') }
      let(:valid_params) { { issue: { text: 'Updated Issue', state: 'ready_for_review' } } }
      let(:valid_put) do
        put "/api/issues/#{issue.id}", params: valid_params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json')
      end

      it 'updates a issue' do
        valid_put
        expect(response.status).to eq(200)

        expect(current_project.issues.find(issue.id).text).to eq valid_params[:issue][:text]

        retrieved_issue = JSON.parse(response.body)
        expect(retrieved_issue['text']).to eq valid_params[:issue][:text]
        expect(retrieved_issue['state']).to eq valid_params[:issue][:state]
      end

      let(:submit_form) { valid_put }
      let(:model) { issue }
      include_examples 'creates an Activity', :update
      include_examples 'sets the whodunnit', :update

      it 'throws 415 unless JSON is sent' do
        params = { issue: { text: 'Bad issuet' } }
        put "/api/issues/#{ issue.id }", params: params, env: @env
        expect(response.status).to eq(415)
      end

      it 'throws 422 if issue is invalid' do
        params = { issue: { text: 'B' * (65535 + 1) } }
        put "/api/issues/#{ issue.id }", params: params.to_json, env: @env.merge('CONTENT_TYPE' => 'application/json')
        expect(response.status).to eq(422)
      end
    end

    describe 'DELETE /api/issue/:id' do
      let(:issue) { create(:issue, node: current_project.issue_library) }

      let(:delete_issue) { delete "/api/issues/#{ issue.id }", env: @env }

      it 'deletes a issue' do
        delete_issue
        expect(response.status).to eq(200)
        expect { current_project.issues.find(issue.id) }.to raise_error(ActiveRecord::RecordNotFound)
      end

      let(:submit_form) { delete_issue }
      let(:model) { issue }
      include_examples 'creates an Activity', :destroy
    end
  end
end
