@infrastructure
Feature: TDD expected-fail handler infrastructure
  Verify that ``apply_tdd_inversion`` (the production code path called by the
  ``Scenario.run()`` monkey-patch) and ``handle_tdd_expected_fail`` (the
  standalone entry point) correctly invert scenario AND step-level status for
  TDD issue-capture tests.

  # --- apply_tdd_inversion (production path) ---

  Scenario: apply_tdd_inversion inverts a failed scenario with failed and skipped steps to passed
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "broken step" with status "failed"
    And the mock scenario has a step "skipped step" with status "skipped"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "passed"
    And the step "broken step" should have status "passed"
    And the step "skipped step" should have status "passed"
    And the step "broken step" should have error_message cleared
    And the step "skipped step" should have error_message cleared

  Scenario: apply_tdd_inversion fails a passed scenario that still carries @tdd_expected_fail
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "passed"
    And the mock scenario has a step "last step" with status "passed"
    When apply_tdd_inversion processes the scenario with failed False
    Then the scenario status should be "failed"
    And the step "last step" should have a synthetic error_message

  Scenario: apply_tdd_inversion does not invert when hook_failed is set
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed" and hook_failed
    And the mock scenario has a step "broken step" with status "failed"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "failed"
    And the step "broken step" should have status "failed"

  Scenario: apply_tdd_inversion does not invert during dry-run mode
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "passed" and was_dry_run
    When apply_tdd_inversion processes the scenario with failed False
    Then the apply_tdd_inversion result should be False

  Scenario: apply_tdd_inversion does not invert non-AssertionError exceptions
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has an infrastructure-error step "infra step" with exception "RuntimeError"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "failed"
    And the step "infra step" should have status "failed"

  Scenario: apply_tdd_inversion surfaces non-AssertionError guard warning to stderr
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has an infrastructure-error step "infra step" with exception "RuntimeError"
    When apply_tdd_inversion processes the scenario with failed True while capturing stderr
    Then the scenario status should be "failed"
    And the step "infra step" should have status "failed"
    And the apply_tdd_inversion result should be True
    And stderr output should contain "Non-assertion exception in expected-fail scenario"

  Scenario: apply_tdd_inversion does not emit guard warning for AssertionError exceptions
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "bug step" with status "failed"
    When apply_tdd_inversion processes the scenario with failed True while capturing stderr
    Then the scenario status should be "passed"
    And the step "bug step" should have status "passed"
    And the apply_tdd_inversion result should be False
    And stderr output should not contain "Non-assertion exception"

  Scenario: apply_tdd_inversion ignores scenarios without @tdd_expected_fail
    Given a mock scenario tagged "@tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "broken step" with status "failed"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "failed"
    And the step "broken step" should have status "failed"

  # --- handle_tdd_expected_fail (standalone entry point) ---

  Scenario: Handler inverts a failed scenario with failed and skipped steps to passed
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "broken step" with status "failed"
    And the mock scenario has a step "skipped step" with status "skipped"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "passed"
    And the step "broken step" should have status "passed"
    And the step "skipped step" should have status "passed"
    And the step "broken step" should have error_message cleared
    And the step "skipped step" should have error_message cleared

  Scenario: Handler fails a passed scenario that still carries @tdd_expected_fail
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "passed"
    And the mock scenario has a step "last step" with status "passed"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"
    And the step "last step" should have a synthetic error_message

  Scenario: Handler rejects @tdd_expected_fail without @tdd_issue
    Given a mock scenario tagged "@tdd_expected_fail" with status "failed"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"

  Scenario: Handler rejects @tdd_expected_fail without @tdd_issue_N
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue" with status "failed"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"

  Scenario: Handler rejects @tdd_issue_N without @tdd_issue unconditionally
    Given a mock scenario tagged "@tdd_issue_999" with status "passed"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"

  Scenario: Handler ignores scenarios without @tdd_expected_fail
    Given a mock scenario tagged "@tdd_issue @tdd_issue_999" with status "failed"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"

  Scenario: Handler does not invert non-AssertionError exceptions
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has an infrastructure-error step "infra step" with exception "RuntimeError"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"
    And the step "infra step" should have status "failed"

  # --- apply_tdd_inversion: mixed exception types (S3) ---

  Scenario: apply_tdd_inversion does not invert when mixed exceptions present
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "bug step" with status "failed"
    And the mock scenario has an infrastructure-error step "infra step" with exception "RuntimeError"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "failed"
    And the step "bug step" should have status "failed"
    And the step "infra step" should have status "failed"

  Scenario: Handler does not invert when mixed exceptions present
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "bug step" with status "failed"
    And the mock scenario has an infrastructure-error step "infra step" with exception "RuntimeError"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "failed"
    And the step "bug step" should have status "failed"
    And the step "infra step" should have status "failed"

  # --- selective step reset preserves already-passed steps (TC-3) ---

  Scenario: apply_tdd_inversion preserves already-passed steps during inversion
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "ok step" with status "passed"
    And the mock scenario has a step "broken step" with status "failed"
    And the mock scenario has a step "skipped step" with status "skipped"
    When apply_tdd_inversion processes the scenario with failed True
    Then the scenario status should be "passed"
    And the step "ok step" should have status "passed"
    And the step "broken step" should have status "passed"
    And the step "skipped step" should have status "passed"
    And the step "broken step" should have error_message cleared
    And the step "skipped step" should have error_message cleared

  Scenario: Handler preserves already-passed steps during inversion
    Given a mock scenario tagged "@tdd_expected_fail @tdd_issue @tdd_issue_999" with status "failed"
    And the mock scenario has a step "ok step" with status "passed"
    And the mock scenario has a step "broken step" with status "failed"
    And the mock scenario has a step "skipped step" with status "skipped"
    When the TDD expected-fail handler processes the scenario
    Then the scenario status should be "passed"
    And the step "ok step" should have status "passed"
    And the step "broken step" should have status "passed"
    And the step "skipped step" should have status "passed"
    And the step "broken step" should have error_message cleared
    And the step "skipped step" should have error_message cleared

  # --- before_scenario hook_failed regression test (TC-2) ---

  Scenario: before_scenario sets hook_failed on invalid TDD tags
    Given a mock scenario tagged "@tdd_expected_fail" with status "untested"
    When before_scenario is called with the mock scenario
    Then the scenario hook_failed flag should be True
    And the scenario status should be "failed"

  # --- _install_tdd_expected_fail_patch (S2) ---

  Scenario: _install_tdd_expected_fail_patch sets the patched flag
    When the TDD expected-fail patch installation status is checked
    Then the Scenario class should have _tdd_run_patched set to True

  Scenario: _install_tdd_expected_fail_patch is idempotent
    When _install_tdd_expected_fail_patch is called twice
    Then the Scenario.run method should not be double-wrapped
