# frozen_string_literal: true

require "logger"
require "stringio"
require "sentry-ruby"
require "test_helper"
require "support/http_helpers"
require "httpx/adapters/sentry"

class SentryTest < Minitest::Test
  include HTTPHelpers

  DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"

  def test_sentry_send_yes_pii
    before_pii = Sentry.configuration.send_default_pii
    begin
      Sentry.configuration.send_default_pii = true

      transaction = Sentry.start_transaction
      Sentry.get_current_scope.set_span(transaction)

      uri = build_uri("/get")

      response = HTTPX.get(uri, params: { "foo" => "bar" })

      verify_status(response, 200)
      verify_spans(transaction, response, description: "GET #{uri}?foo=bar")
      crumb = Sentry.get_current_scope.breadcrumbs.peek
      assert crumb.category == "httpx"
      assert crumb.data ==  { status: 200, method: "GET", url: "#{uri}?foo=bar" }
    ensure
      Sentry.configuration.send_default_pii = before_pii
    end
  end

  def test_sentry_send_no_pii
    before_pii = Sentry.configuration.send_default_pii
    begin
      Sentry.configuration.send_default_pii = false

      transaction = Sentry.start_transaction
      Sentry.get_current_scope.set_span(transaction)

      uri = build_uri("/get")

      response = HTTPX.get(uri, params: { "foo" => "bar" })

      verify_status(response, 200)
      verify_spans(transaction, response, description: "GET #{uri}")
      crumb = Sentry.get_current_scope.breadcrumbs.peek
      assert crumb.category == "httpx"
      assert crumb.data ==  { status: 200, method: "GET", url: uri }
    ensure
      Sentry.configuration.send_default_pii = before_pii
    end
  end

  def test_sentry_post_request
    before_pii = Sentry.configuration.send_default_pii
    begin
      Sentry.configuration.send_default_pii = true
      transaction = Sentry.start_transaction
      Sentry.get_current_scope.set_span(transaction)

      uri = build_uri("/post")
      response = HTTPX.post(uri, form: { foo: "bar" })
      verify_status(response, 200)
      verify_spans(transaction, response, verb: "POST")

      crumb = Sentry.get_current_scope.breadcrumbs.peek
      assert crumb.category == "httpx"
      assert crumb.data == { status: 200, method: "POST", url: uri, body: "foo=bar" }
    ensure
      Sentry.configuration.send_default_pii = before_pii
    end
  end

  def test_sentry_multiple_requests
    transaction = Sentry.start_transaction
    Sentry.get_current_scope.set_span(transaction)

    responses = HTTPX.get(build_uri("/status/200"), build_uri("/status/404"))
    verify_status(responses[0], 200)
    verify_status(responses[1], 404)
    verify_spans(transaction, *responses)
  end

  def test_sentry_server_error_request
    transaction = Sentry.start_transaction
    Sentry.get_current_scope.set_span(transaction)

    uri = URI("http://unexisting/")

    response = HTTPX.get(uri)

    verify_error_response(response, /name or service not known/)
    assert response.is_a?(HTTPX::ErrorResponse), "response should contain errors"
    verify_spans(transaction, response, verb: "GET")
    crumb = Sentry.get_current_scope.breadcrumbs.peek
    assert crumb.category == "httpx"
    assert crumb.data == { error: "name or service not known", method: "GET", url: uri.to_s }
  end

  private

  def verify_spans(transaction, *responses, verb: nil, description: nil)
    assert transaction.span_recorder.spans.count == responses.size + 1
    assert transaction.span_recorder.spans[0] == transaction

    response_spans = transaction.span_recorder.spans[1..-1]

    responses.each_with_index do |response, idx|
      request_span = response_spans[idx]
      assert request_span.op == "httpx.client"
      assert !request_span.start_timestamp.nil?
      assert !request_span.timestamp.nil?
      assert request_span.start_timestamp != request_span.timestamp
      assert request_span.description == (description || "#{verb || "GET"} #{response.uri}")
      if response.is_a?(HTTPX::ErrorResponse)
        assert request_span.data == { error: response.error.message }
      else
        assert request_span.data == { status: response.status }
      end
    end
  end

  def setup
    super

    mock_io = StringIO.new
    mock_logger = Logger.new(mock_io)

    Sentry.init do |config|
      config.traces_sample_rate = 1.0
      config.sdk_logger = mock_logger
      config.dsn = DUMMY_DSN
      config.transport.transport_class = Sentry::DummyTransport
      config.background_worker_threads = 0
      config.breadcrumbs_logger = [:http_logger]
      config.enabled_patches << :httpx
      # so the events will be sent synchronously for testing
    end
  end

  def origin
    "https://#{httpbin}"
  end
end
