spec/lib/appsignal/transaction_spec.rb in appsignal-2.2.1 vs spec/lib/appsignal/transaction_spec.rb in appsignal-2.3.0.beta.1
- old
+ new
@@ -1,156 +1,217 @@
describe Appsignal::Transaction do
before :context do
start_agent
end
- let(:time) { Time.at(fixed_time) }
- let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
- let(:env) { {} }
- let(:merged_env) { http_request_env_with_data(env) }
- let(:options) { {} }
- let(:request) { Rack::Request.new(merged_env) }
- let(:transaction) { Appsignal::Transaction.new("1", namespace, request, options) }
+ let(:transaction_id) { "1" }
+ let(:time) { Time.at(fixed_time) }
+ let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
+ let(:env) { {} }
+ let(:merged_env) { http_request_env_with_data(env) }
+ let(:options) { {} }
+ let(:request) { Rack::Request.new(merged_env) }
+ let(:transaction) { Appsignal::Transaction.new(transaction_id, namespace, request, options) }
+ let(:log) { StringIO.new }
before { Timecop.freeze(time) }
- after { Timecop.return }
+ after { Timecop.return }
+ around do |example|
+ use_logger_with log do
+ example.run
+ end
+ end
describe "class methods" do
+ def current_transaction
+ Appsignal::Transaction.current
+ end
+
describe ".create" do
- it "should add the transaction to thread local" do
- expect(Appsignal::Extension).to receive(:start_transaction).with("1", "http_request", 0)
+ def create_transaction(id = transaction_id)
+ Appsignal::Transaction.create(id, namespace, request, options)
+ end
- created_transaction = Appsignal::Transaction.create("1", namespace, request, options)
+ context "when no transaction is running" do
+ let!(:transaction) { create_transaction }
- expect(Thread.current[:appsignal_transaction]).to eq created_transaction
- end
+ it "returns the created transaction" do
+ expect(transaction).to be_a Appsignal::Transaction
+ expect(transaction.transaction_id).to eq transaction_id
+ expect(transaction.namespace).to eq namespace
+ expect(transaction.request).to eq request
- it "should create a transaction" do
- created_transaction = Appsignal::Transaction.create("1", namespace, request, options)
+ expect(transaction.to_h).to include(
+ "id" => transaction_id,
+ "namespace" => namespace
+ )
+ end
- expect(created_transaction).to be_a Appsignal::Transaction
- expect(created_transaction.transaction_id).to eq "1"
- expect(created_transaction.namespace).to eq "http_request"
+ it "assigns the transaction to current" do
+ expect(transaction).to eq current_transaction
+ end
end
context "when a transaction is already running" do
- let(:running_transaction) { double(:transaction_id => 2) }
- before { Thread.current[:appsignal_transaction] = running_transaction }
+ before { create_transaction }
- it "should not create a new transaction" do
- expect(
- Appsignal::Transaction.create("1", namespace, request, options)
- ).to eq(running_transaction)
+ it "does not create a new transaction, but returns the current transaction" do
+ expect do
+ new_transaction = create_transaction("2")
+ expect(new_transaction).to eq(current_transaction)
+ expect(new_transaction.transaction_id).to eq(transaction_id)
+ end.to_not change { current_transaction }
end
- it "should output a debug message" do
- expect(Appsignal.logger).to receive(:debug)
- .with("Trying to start new transaction 1 but 2 is already running. Using 2")
-
- Appsignal::Transaction.create("1", namespace, request, options)
+ it "logs a debug message" do
+ create_transaction("2")
+ expect(log_contents(log)).to contains_log :debug,
+ "Trying to start new transaction with id '2', but a " \
+ "transaction with id '#{transaction_id}' is already " \
+ "running. Using transaction '#{transaction_id}'."
end
- context "with option to force a new transaction" do
- let(:options) { { :force => true } }
- it "should not create a new transaction" do
- expect(
- Appsignal::Transaction.create("1", namespace, request, options)
- ).to_not eq(running_transaction)
+ context "with option :force => true" do
+ it "returns the newly created (and current) transaction" do
+ original_transaction = current_transaction
+ expect(original_transaction).to_not be_nil
+ expect(current_transaction.transaction_id).to eq transaction_id
+
+ options[:force] = true
+ expect(create_transaction("2")).to_not eq original_transaction
+ expect(current_transaction.transaction_id).to eq "2"
end
end
end
end
describe ".current" do
- before { Thread.current[:appsignal_transaction] = transaction }
-
subject { Appsignal::Transaction.current }
- context "if there is a transaction" do
- before { Appsignal::Transaction.create("1", namespace, request, options) }
+ context "when there is a current transaction" do
+ let!(:transaction) do
+ Appsignal::Transaction.create(transaction_id, namespace, request, options)
+ end
- it "should return the correct transaction" do
- is_expected.to eq transaction
+ it "reads :appsignal_transaction from the current Thread" do
+ expect(subject).to eq Thread.current[:appsignal_transaction]
+ expect(subject).to eq transaction
end
- it "should indicate it's not a nil transaction" do
- expect(subject.nil_transaction?).to be_falsy
+ it "is not a NilTransaction" do
+ expect(subject.nil_transaction?).to eq false
+ expect(subject).to be_a Appsignal::Transaction
end
end
- context "if there is no transaction" do
- before do
- Thread.current[:appsignal_transaction] = nil
+ context "when there is no current transaction" do
+ it "has no :appsignal_transaction registered on the current Thread" do
+ expect(Thread.current[:appsignal_transaction]).to be_nil
end
- it "should return a nil transaction stub" do
- is_expected.to be_a Appsignal::Transaction::NilTransaction
+ it "returns a NilTransaction stub" do
+ expect(subject.nil_transaction?).to eq true
+ expect(subject).to be_a Appsignal::Transaction::NilTransaction
end
-
- it "should indicate it's a nil transaction" do
- expect(subject.nil_transaction?).to be_truthy
- end
end
end
- describe "complete_current!" do
- before { Appsignal::Transaction.create("2", Appsignal::Transaction::HTTP_REQUEST, {}) }
+ describe ".complete_current!" do
+ let!(:transaction) { Appsignal::Transaction.create(transaction_id, namespace, options) }
- it "should complete the current transaction and set the thread appsignal_transaction to nil" do
- expect(Appsignal::Transaction.current).to receive(:complete)
+ it "completes the current transaction" do
+ expect(transaction).to eq current_transaction
+ expect(transaction).to receive(:complete).and_call_original
Appsignal::Transaction.complete_current!
-
- expect(Thread.current[:appsignal_transaction]).to be_nil
end
- it "should still clear the transaction if there is an error" do
- expect(Appsignal::Transaction.current).to receive(:complete).and_raise "Error"
-
- Appsignal::Transaction.complete_current!
-
- expect(Thread.current[:appsignal_transaction]).to be_nil
+ it "unsets the current transaction on the current Thread" do
+ expect do
+ Appsignal::Transaction.complete_current!
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
end
- context "if a transaction is discarded" do
- it "should not complete the transaction" do
- expect(Appsignal::Transaction.current.ext).to_not receive(:complete)
+ context "when encountering an error while completing" do
+ before do
+ expect(transaction).to receive(:complete).and_raise VerySpecificError
+ end
- Appsignal::Transaction.current.discard!
- expect(Appsignal::Transaction.current.discarded?).to be_truthy
-
+ it "logs an error message" do
Appsignal::Transaction.complete_current!
-
- expect(Thread.current[:appsignal_transaction]).to be_nil
+ expect(log_contents(log)).to contains_log :error,
+ "Failed to complete transaction ##{transaction.transaction_id}. VerySpecificError"
end
- it "should not be discarded when restore! is called" do
- Appsignal::Transaction.current.discard!
- expect(Appsignal::Transaction.current.discarded?).to be_truthy
- Appsignal::Transaction.current.restore!
- expect(Appsignal::Transaction.current.discarded?).to be_falsy
+ it "clears the current transaction" do
+ expect do
+ Appsignal::Transaction.complete_current!
+ end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
end
end
end
end
describe "#complete" do
- it "should sample data if it needs to be sampled" do
- expect(transaction.ext).to receive(:finish).and_return(true)
- expect(transaction).to receive(:sample_data)
- expect(transaction.ext).to receive(:complete)
+ context "when transaction is being sampled" do
+ it "samples data" do
+ expect(transaction.ext).to receive(:finish).and_return(true)
+ # Stub call to extension, because that would remove the transaction
+ # from the extension.
+ expect(transaction.ext).to receive(:complete)
- transaction.complete
+ transaction.set_tags(:foo => "bar")
+ transaction.complete
+ expect(transaction.to_h["sample_data"]).to include(
+ "tags" => { "foo" => "bar" }
+ )
+ end
end
- it "should not sample data if it does not need to be sampled" do
- expect(transaction.ext).to receive(:finish).and_return(false)
- expect(transaction).to_not receive(:sample_data)
- expect(transaction.ext).to receive(:complete)
+ context "when transaction is not being sampled" do
+ it "does not sample data" do
+ expect(transaction).to_not receive(:sample_data)
+ expect(transaction.ext).to receive(:finish).and_return(false)
+ expect(transaction.ext).to receive(:complete).and_call_original
- transaction.complete
+ transaction.complete
+ end
end
+
+ context "when a transaction is marked as discarded" do
+ it "does not complete the transaction" do
+ expect(transaction.ext).to_not receive(:complete)
+
+ expect do
+ transaction.discard!
+ end.to change { transaction.discarded? }.from(false).to(true)
+
+ transaction.complete
+ end
+
+ it "logs a debug message" do
+ transaction.discard!
+ transaction.complete
+
+ expect(log_contents(log)).to contains_log :debug,
+ "Skipping transaction '#{transaction_id}' because it was manually discarded."
+ end
+
+ context "when a discarded transaction is restored" do
+ before { transaction.discard! }
+
+ it "completes the transaction" do
+ expect(transaction.ext).to receive(:complete).and_call_original
+
+ expect do
+ transaction.restore!
+ end.to change { transaction.discarded? }.from(true).to(false)
+
+ transaction.complete
+ end
+ end
+ end
end
context "pausing" do
describe "#pause!" do
it "should change the pause flag to true" do
@@ -239,10 +300,41 @@
expect(transaction.store("test")).to eql("transaction" => "value")
end
end
+ describe "#params" do
+ subject { transaction.params }
+
+ context "with custom params set on transaction" do
+ before do
+ transaction.params = { :foo => "bar" }
+ end
+
+ it "returns custom parameters" do
+ expect(subject).to eq(:foo => "bar")
+ end
+ end
+
+ context "without custom params set on transaction" do
+ it "returns parameters from request" do
+ expect(subject).to eq(
+ "action" => "show",
+ "controller" => "blog_posts",
+ "id" => "1"
+ )
+ end
+ end
+ end
+
+ describe "#params=" do
+ it "sets params on the transaction" do
+ transaction.params = { :foo => "bar" }
+ expect(transaction.params).to eq(:foo => "bar")
+ end
+ end
+
describe "#set_tags" do
it "should add tags to transaction" do
expect do
transaction.set_tags("a" => "b")
end.to change(transaction, :tags).to("a" => "b")
@@ -731,23 +823,42 @@
end
describe "#sanitized_params" do
subject { transaction.send(:sanitized_params) }
- context "without params" do
+ context "with custom params" do
+ before do
+ transaction.params = { :foo => "bar", :baz => :bat }
+ end
+
+ it "returns custom params" do
+ is_expected.to eq(:foo => "bar", :baz => :bat)
+ end
+
+ context "with AppSignal filtering" do
+ before { Appsignal.config.config_hash[:filter_parameters] = %w(foo) }
+ after { Appsignal.config.config_hash[:filter_parameters] = [] }
+
+ it "returns sanitized custom params" do
+ expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
+ end
+ end
+ end
+
+ context "without request params" do
before { allow(transaction.request).to receive(:params).and_return(nil) }
it { is_expected.to be_nil }
end
- context "when params crashes" do
+ context "when request params crashes" do
before { allow(transaction.request).to receive(:params).and_raise(NoMethodError) }
it { is_expected.to be_nil }
end
- context "when params method does not exist" do
+ context "when request params method does not exist" do
let(:options) { { :params_method => :nonsense } }
it { is_expected.to be_nil }
end
@@ -976,9 +1087,37 @@
line.tr("2", "?")
end
expect(subject).to eq ["line 1", "line ?"]
end
end
+ end
+ end
+ end
+
+ describe ".to_hash / .to_h" do
+ subject { transaction.to_hash }
+
+ context "when extension returns serialized JSON" do
+ it "parses the result and returns a Hash" do
+ expect(subject).to include(
+ "action" => nil,
+ "error" => nil,
+ "events" => [],
+ "id" => transaction_id,
+ "metadata" => {},
+ "namespace" => namespace,
+ "sample_data" => {}
+ )
+ end
+ end
+
+ context "when the extension returns invalid serialized JSON" do
+ before do
+ expect(transaction.ext).to receive(:to_json).and_return("foo")
+ end
+
+ it "raises a JSON parse error" do
+ expect { subject }.to raise_error(JSON::ParserError)
end
end
end
describe Appsignal::Transaction::NilTransaction do