# encoding: utf-8
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.

require 'new_relic/agent/datastores'
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))

class NewRelic::Agent::DatastoresTest < Minitest::Test
  class MyFirstDatabase
    THE_OBJECT = Object.new

    def find
      THE_OBJECT
    end

    def save
    end

    def internal
    end

    private :internal

    NewRelic::Agent::Datastores.trace self, :find,     "MyFirstDatabase"
    NewRelic::Agent::Datastores.trace self, :save,     "MyFirstDatabase", "create"
    NewRelic::Agent::Datastores.trace self, :internal, "MyFirstDatabase"

    def boom
      raise "haha"
    end

    NewRelic::Agent::Datastores.trace self, :boom, "MyFirstDatabase", "boom"
  end

  def setup
    NewRelic::Agent.drop_buffered_data
  end

  def test_still_calls_through
    assert_equal MyFirstDatabase::THE_OBJECT, MyFirstDatabase.new.find
  end

  def test_in_web_transaction
    in_web_transaction("txn") do
      MyFirstDatabase.new.find
    end

    assert_metrics("find", "Web")
  end

  def test_in_other_transaction
    in_background_transaction("txn") do
      MyFirstDatabase.new.find
    end

    assert_metrics("find", "Other")
  end

  def test_outside_transaction
    MyFirstDatabase.new.find
    assert_metrics_recorded([
                            "Datastore/operation/MyFirstDatabase/find",
                            "Datastore/MyFirstDatabase/allOther",
                            "Datastore/MyFirstDatabase/all",
                            "Datastore/allOther",
                            "Datastore/all"])
  end

  def test_separate_operation_name
    in_background_transaction("txn") do
      MyFirstDatabase.new.save
    end

    assert_metrics("create", "Other")
  end

  def test_safe_to_reinstrument
    MyFirstDatabase.class_eval do
      NewRelic::Agent::Datastores.trace self, :find, "MyFirstDatabase", "find"
    end

    assert_equal MyFirstDatabase::THE_OBJECT, MyFirstDatabase.new.find
  end

  def test_method_retains_visbility
    private_methods = MyFirstDatabase.private_instance_methods.map(&:to_sym)
    assert_includes private_methods, :internal
  end

  def test_wrap_doesnt_interfere
    result = NewRelic::Agent::Datastores.wrap("MyFirstDatabase", "op") do
      "yo"
    end

    assert_equal "yo", result
  end

  def test_wrap
    in_background_transaction("txn") do
      NewRelic::Agent::Datastores.wrap("MyFirstDatabase", "op", "coll") do
      end
    end

    assert_statement_metrics("op", "coll", "Other")
  end

  def test_wrap_with_only_operation
    in_background_transaction("txn") do
      NewRelic::Agent::Datastores.wrap("MyFirstDatabase", "op") do
      end
    end

    assert_metrics("op", "Other")
  end

  def test_wrap_with_no_operation
    in_background_transaction("txn") do
      NewRelic::Agent::Datastores.wrap("MyFirstDatabase", nil) do
      end
    end

    refute_metrics_recorded([
                            "Datastore/operation/MyFirstDatabase/",
                            "Datastore/MyFirstDatabase/allOther",
                            "Datastore/MyFirstDatabase/all",
                            "Datastore/allOther",
                            "Datastore/all"])
  end

  def test_wrap_calls_notice
    noticed = nil
    notice = Proc.new do |*args|
      noticed = args
    end

    NewRelic::Agent::Datastores.wrap("MyFirstDatabase", "op", "coll", notice) do
      "yo"
    end

    result, scoped_metric, elapsed = noticed

    assert_equal "yo", result
    assert_equal "Datastore/statement/MyFirstDatabase/coll/op", scoped_metric
    assert_instance_of Float, elapsed
  end

  def test_notice_sql
    query   = "SELECT * FROM SomeThings"
    metric  = "Datastore/statement/MyFirstDatabase/SomeThing/find"
    elapsed = 1.0

    agent = NewRelic::Agent.instance
    agent.transaction_sampler.expects(:notice_sql).with(query, nil, elapsed)
    agent.sql_sampler.expects(:notice_sql).with(query, metric, nil, elapsed)

    NewRelic::Agent::Datastores.notice_sql(query, metric, elapsed)
  end

  def test_notice_statement
    query   = "key"
    elapsed = 1.0

    agent = NewRelic::Agent.instance
    agent.transaction_sampler.expects(:notice_nosql_statement).with(query, elapsed)

    NewRelic::Agent::Datastores.notice_statement(query, elapsed)
  end

  def test_dont_notice_statement_based_on_record_sql_setting
    query   = "key"
    elapsed = 1.0

    agent = NewRelic::Agent.instance
    agent.transaction_sampler.expects(:notice_nosql_statement).never

    with_config(:'transaction_tracer.record_sql' => 'none') do
      NewRelic::Agent::Datastores.notice_statement(query, elapsed)
    end
  end

  def test_node_created_when_exception_occurs
    db = MyFirstDatabase.new
    in_transaction do
      db.find
      db.boom
    end
  rescue
    sample = NewRelic::Agent.instance.transaction_sampler.last_sample
    refute_nil find_node_with_name(sample, "Datastore/operation/MyFirstDatabase/boom")
  end

  def assert_statement_metrics(operation, collection, type)
    assert_metrics_recorded([
                            "Datastore/statement/MyFirstDatabase/#{collection}/#{operation}",
                            ["Datastore/statement/MyFirstDatabase/#{collection}/#{operation}", "txn"],
                            "Datastore/operation/MyFirstDatabase/#{operation}",
                            "Datastore/MyFirstDatabase/all#{type}",
                            "Datastore/MyFirstDatabase/all",
                            "Datastore/all#{type}",
                            "Datastore/all"])
  end

  def assert_metrics(operation, type)
    assert_metrics_recorded([
                            "Datastore/operation/MyFirstDatabase/#{operation}",
                            ["Datastore/operation/MyFirstDatabase/#{operation}", "txn"],
                            "Datastore/MyFirstDatabase/all#{type}",
                            "Datastore/MyFirstDatabase/all",
                            "Datastore/all#{type}",
                            "Datastore/all"])
  end
end
