# frozen_string_literal: true

require 'spec_helper'
require 'cucumber/formatter/spec_helper'
require 'cucumber/formatter/json'
require 'cucumber/cli/options'
require 'json'

module Cucumber
  module Formatter
    describe Json do
      extend SpecHelperDsl
      include SpecHelper

      context 'Given a single feature' do
        before(:each) do
          @out = StringIO.new
          @formatter = Json.new(actual_runtime.configuration.with_options(out_stream: @out))
          run_defined_feature
        end

        describe 'with a scenario with an undefined step' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          it 'outputs the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec.feature:4"},
                      "result": {"status": "undefined"}}]}]}]))
          end
        end

        describe 'with a scenario with a passed step' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) {}
          end

          it 'outputs the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:63"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario with a failed step' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { raise 'no bananas' }
          end

          it 'outputs the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%{
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:100"},
                      "result": {"status": "failed",
                                 "error_message": "no bananas (RuntimeError)\\n./spec/cucumber/formatter/json_spec.rb:100:in `/^there are bananas$/'\\nspec.feature:4:in `there are bananas'",
                                 "duration": 1}}]}]}]})
          end
        end

        describe 'with a scenario with a pending step' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { pending }
          end

          it 'outputs the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%{
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:138"},
                      "result": {"status": "pending",
                                 "error_message": "TODO (Cucumber::Pending)\\n./spec/cucumber/formatter/json_spec.rb:138:in `/^there are bananas$/'\\nspec.feature:4:in `there are bananas'",
                                 "duration": 1}}]}]}]})
          end
        end

        describe 'with a scenario outline with one example' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario Outline: Monkey eats <fruit>
              Given there are <fruit>

              Examples: Fruit Table
              |  fruit  |
              | bananas |
          FEATURE

          define_steps do
            Given(/^there are bananas$/) {}
          end

          it 'outputs the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-<fruit>;fruit-table;2",
                   "keyword": "Scenario Outline",
                   "name": "Monkey eats bananas",
                   "line": 8,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:180"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with tags in the feature file' do
          define_feature <<-FEATURE
          @f
          Feature: Banana party

            @s
            Scenario: Monkey eats bananas
              Given there are bananas

            @so
            Scenario Outline: Monkey eats bananas
              Given there are <fruit>

              @ex
              Examples: Fruit Table
              |  fruit  |
              | bananas |
          FEATURE

          define_steps do
            Given(/^there are bananas$/) {}
          end

          it 'the tags are included in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 2,
                "description": "",
                "tags": [{"name": "@f",
                          "line": 1}],
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 5,
                   "description": "",
                   "tags": [{"name": "@f",
                             "line": 1},
                            {"name": "@s",
                             "line": 4}],
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 6,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:228"},
                      "result": {"status": "passed",
                                 "duration": 1}}]},
                   {"id": "banana-party;monkey-eats-bananas;fruit-table;2",
                   "keyword": "Scenario Outline",
                   "name": "Monkey eats bananas",
                   "line": 15,
                   "description": "",
                   "tags": [{"name": "@f",
                             "line": 1},
                            {"name": "@so",
                             "line": 8},
                            {"name": "@ex",
                             "line": 12}],
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 10,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:228"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with comments in the feature file' do
          define_feature <<-FEATURE
          #feature comment
          Feature: Banana party

            #background comment
            Background: There are bananas
              Given there are bananas

            #scenario comment
            Scenario: Monkey eats bananas
              #step comment1
              Then the monkey eats bananas

            #scenario outline comment
            Scenario Outline: Monkey eats bananas
              #step comment2
              Then the monkey eats <fruit>

              #examples table comment
              Examples: Fruit Table
              |  fruit  |
              #examples table row comment
              | bananas |
          FEATURE

          define_steps do
            Given(/^there are bananas$/) {}
            Then(/^the monkey eats bananas$/) {}
          end

          it 'the comments are not included in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 2,
                "description": "",
                "elements":
                 [{"keyword": "Background",
                   "name": "There are bananas",
                   "line": 5,
                   "description": "",
                   "type": "background",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 6,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:308"},
                      "result": {"status": "passed",
                                 "duration": 1}}]},
                  {"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 9,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Then ",
                      "name": "the monkey eats bananas",
                      "line": 11,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:309"},
                      "result": {"status": "passed",
                                 "duration": 1}}]},
                  {"keyword": "Background",
                   "name": "There are bananas",
                   "line": 5,
                   "description": "",
                   "type": "background",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 6,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:308"},
                      "result": {"status": "passed",
                                 "duration": 1}}]},
                   {"id": "banana-party;monkey-eats-bananas;fruit-table;2",
                   "keyword": "Scenario Outline",
                   "name": "Monkey eats bananas",
                   "line": 22,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Then ",
                      "name": "the monkey eats bananas",
                      "line": 16,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:309"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario with a step with a doc string' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
                """
                the doc string
                """
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { |s| s }
          end

          it 'includes the doc string in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "doc_string": {"value": "the doc string",
                                     "content_type": "",
                                     "line": 5},
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:386"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario with a step that use puts' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { log 'from step' }
          end

          it 'includes the output from the step in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "output": ["from step"],
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:426"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a background' do
          define_feature <<-FEATURE
          Feature: Banana party

            Background: There are bananas
              Given there are bananas

            Scenario: Monkey eats bananas
              Then the monkey eats bananas

            Scenario: Monkey eats more bananas
              Then the monkey eats more bananas
          FEATURE

          it 'includes the background in the json data each time it is executed' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"keyword": "Background",
                   "name": "There are bananas",
                   "line": 3,
                   "description": "",
                   "type": "background",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec.feature:4"},
                      "result": {"status": "undefined"}}]},
                  {"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 6,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Then ",
                      "name": "the monkey eats bananas",
                      "line": 7,
                      "match": {"location": "spec.feature:7"},
                      "result": {"status": "undefined"}}]},
                  {"keyword": "Background",
                   "name": "There are bananas",
                   "line": 3,
                   "description": "",
                   "type": "background",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec.feature:4"},
                      "result": {"status": "undefined"}}]},
                  {"id": "banana-party;monkey-eats-more-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats more bananas",
                   "line": 9,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Then ",
                      "name": "the monkey eats more bananas",
                      "line": 10,
                      "match": {"location": "spec.feature:10"},
                      "result": {"status": "undefined"}}]}]}]))
          end
        end

        describe 'with a scenario with a step that embeds data directly' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { attach('YWJj', 'mime-type;base64') }
          end

          it 'includes the data from the step in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "embeddings": [{"mime_type": "mime-type",
                                      "data": "YWJj"}],
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:536"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario with a step that embeds a file' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Given(/^there are bananas$/) do
              RSpec::Mocks.allow_message(File, :file?) { true }
              RSpec::Mocks.allow_message(File, :read) { 'foo' }
              attach('out/snapshot.jpeg', 'image/png')
            end
          end

          it 'includes the file content in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "embeddings": [{"mime_type": "image/png",
                                      "data": "Zm9v"}],
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:575"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario with hooks' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Before() {}
            Before() {}
            After() {}
            After() {}
            AfterStep() {}
            AfterStep() {}
            Around() { |_scenario, block| block.call }
            Given(/^there are bananas$/) {}
          end

          it 'includes all hooks except the around hook in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "before":
                    [{"match": {"location": "spec/cucumber/formatter/json_spec.rb:618"},
                      "result": {"status": "passed",
                                 "duration": 1}},
                     {"match": {"location": "spec/cucumber/formatter/json_spec.rb:619"},
                      "result": {"status": "passed",
                                 "duration": 1}}],
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:625"},
                      "result": {"status": "passed",
                                 "duration": 1},
                      "after":
                       [{"match": {"location": "spec/cucumber/formatter/json_spec.rb:622"},
                         "result": {"status": "passed",
                                    "duration": 1}},
                        {"match": {"location": "spec/cucumber/formatter/json_spec.rb:623"},
                         "result": {"status": "passed",
                                    "duration": 1}}]}],
                   "after":
                    [{"match": {"location": "spec/cucumber/formatter/json_spec.rb:621"},
                      "result": {"status": "passed",
                                 "duration": 1}},
                     {"match": {"location": "spec/cucumber/formatter/json_spec.rb:620"},
                      "result": {"status": "passed",
                                 "duration": 1}}]}]}]))
          end
        end

        describe 'with a scenario when only an around hook is failing' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
          FEATURE

          define_steps do
            Around() do |_scenario, block|
              block.call
              raise 'error'
            end
            Given(/^there are bananas$/) {}
          end

          it 'includes the around hook result in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%{
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                    [{"keyword": "Given ",
                      "name": "there are bananas",
                      "line": 4,
                      "match": {"location": "spec/cucumber/formatter/json_spec.rb:687"},
                      "result": {"status": "passed",
                                 "duration": 1}}],
                   "around":
                    [{"match": {"location": "unknown_hook_location:1"},
                      "result": {"status": "failed",
                                 "error_message": "error (RuntimeError)\\n./spec/cucumber/formatter/json_spec.rb:685:in `Around'",
                                 "duration": 1}}]}]}]})
          end
        end

        describe 'with a scenario with a step with a data table' do
          define_feature <<-FEATURE
          Feature: Banana party

            Scenario: Monkey eats bananas
              Given there are bananas
                | aa | bb |
                | cc | dd |
          FEATURE

          define_steps do
            Given(/^there are bananas$/) { |s| s }
          end

          it 'includes the doc string in the json data' do
            expect(load_normalised_json(@out)).to eq JSON.parse(%(
              [{"id": "banana-party",
                "uri": "spec.feature",
                "keyword": "Feature",
                "name": "Banana party",
                "line": 1,
                "description": "",
                "elements":
                 [{"id": "banana-party;monkey-eats-bananas",
                   "keyword": "Scenario",
                   "name": "Monkey eats bananas",
                   "line": 3,
                   "description": "",
                   "type": "scenario",
                   "steps":
                     [{"keyword": "Given ",
                       "name": "there are bananas",
                       "line": 4,
                       "rows":
                         [{"cells": ["aa", "bb"]},
                          {"cells": ["cc", "dd"]}],
                       "match": {"location": "spec/cucumber/formatter/json_spec.rb:731"},
                       "result": {"status": "passed",
                                  "duration": 1}}]}]}]))
          end
        end
      end

      def load_normalised_json(out)
        normalise_json(JSON.parse(out.string))
      end

      def normalise_json(json)
        # make sure duration was captured (should be >= 0)
        # then set it to what is "expected" since duration is dynamic
        json.each do |feature|
          elements = feature.fetch('elements') { [] }
          elements.each do |scenario|
            %w[steps before after around].each do |type|
              next unless scenario[type]
              scenario[type].each do |step_or_hook|
                normalise_json_step_or_hook(step_or_hook)
                next unless step_or_hook['after']
                step_or_hook['after'].each do |hook|
                  normalise_json_step_or_hook(hook)
                end
              end
            end
          end
        end
      end

      def normalise_json_step_or_hook(step_or_hook)
        return unless step_or_hook['result'] && step_or_hook['result']['duration']

        expect(step_or_hook['result']['duration']).to be >= 0
        step_or_hook['result']['duration'] = 1
      end
    end
  end
end
