Initial test suite and helpers

Stephen Paul Weber created

Change summary

.builds/debian-stable.yml |   5 +
Gemfile                   |   9 +++
Rakefile                  |  22 ++++++++
config.ru                 |   5 ++
test/test_electrum.rb     | 102 +++++++++++++++++++++++++++++++++++++++++
test/test_helper.rb       |  22 ++++++++
6 files changed, 164 insertions(+), 1 deletion(-)

Detailed changes

.builds/debian-stable.yml 🔗

@@ -13,7 +13,10 @@ environment:
 tasks:
 - dependencies: |
     cd jmp-pay
-    bundle install --path=.gems
+    bundle install --without=development --path=.gems
 - lint: |
     cd jmp-pay
     rubocop
+- test: |
+    cd jmp-pay
+    RANTLY_COUNT=100 bundle exec rake test

Gemfile 🔗

@@ -12,5 +12,14 @@ gem "roda"
 gem "slim"
 
 group(:development) do
+	gem "pry-reload"
+	gem "pry-rescue"
+	gem "pry-stack_explorer"
 	gem "roda-bin"
 end
+
+group(:test) do
+	gem "minitest"
+	gem "rantly"
+	gem "webmock"
+end

Rakefile 🔗

@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "rake/testtask"
+require "rubocop/rake_task"
+
+Rake::TestTask.new(:test) do |t|
+	ENV["RANTLY_VERBOSE"] = "0" unless ENV["RANTLY_VERBOSE"]
+	ENV["RANTLY_COUNT"] = "10" unless ENV["RANTLY_COUNT"]
+
+	t.libs << "test"
+	t.libs << "lib"
+	t.test_files = FileList["test/**/test_*.rb"]
+	t.warning = false
+end
+
+RuboCop::RakeTask.new(:lint)
+
+task :entr do
+	sh "sh", "-c", "git ls-files | entr -s 'rubocop && rake test'"
+end
+
+task default: :test

config.ru 🔗

@@ -7,6 +7,11 @@ require "pg"
 require "redis"
 require "roda"
 
+if ENV["RACK_ENV"] == "development"
+	require "pry-rescue"
+	use PryRescue::Rack
+end
+
 require_relative "lib/electrum"
 
 REDIS = Redis.new

test/test_electrum.rb 🔗

@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "electrum"
+
+class ElectrumTest < Minitest::Test
+	RPC_URI = "http://example.com"
+
+	def setup
+		@electrum = Electrum.new(
+			rpc_uri: RPC_URI,
+			rpc_username: "username",
+			rpc_password: "password"
+		)
+	end
+
+	def stub_rpc(method, params)
+		stub_request(:post, RPC_URI).with(
+			headers: {"Content-Type" => "application/json"},
+			basic_auth: ["username", "password"],
+			body: hash_including(
+				method: method,
+				params: params
+			)
+		)
+	end
+
+	property(:getaddresshistory) { string(:alnum) }
+	def getaddresshistory(address)
+		req =
+			stub_rpc("getaddresshistory", address: address)
+			.to_return(body: {result: "result"}.to_json)
+		assert_equal "result", @electrum.getaddresshistory(address)
+		assert_requested(req)
+	end
+
+	property(:get_tx_status) { string(:alnum) }
+	def get_tx_status(tx_hash)
+		req =
+			stub_rpc("get_tx_status", txid: tx_hash)
+			.to_return(body: {result: "result"}.to_json)
+		assert_equal "result", @electrum.get_tx_status(tx_hash)
+		assert_requested(req)
+	end
+
+	property(:gettransaction) { [string(:alnum), string(:xdigit)] }
+	def gettransaction(tx_hash, dummy_tx)
+		req1 =
+			stub_rpc("gettransaction", txid: tx_hash)
+			.to_return(body: {result: dummy_tx}.to_json)
+		req2 =
+			stub_rpc("deserialize", [dummy_tx])
+			.to_return(body: {result: {outputs: []}}.to_json)
+		assert_kind_of Electrum::Transaction, @electrum.gettransaction(tx_hash)
+		assert_requested(req1)
+		assert_requested(req2)
+	end
+
+	class TransactionTest < Minitest::Test
+		def transaction(outputs=[])
+			electrum_mock = Minitest::Mock.new("Electrum")
+			[
+				electrum_mock,
+				Electrum::Transaction.new(
+					electrum_mock,
+					"txhash",
+					"outputs" => outputs
+				)
+			]
+		end
+
+		def test_confirmations
+			electrum_mock, tx = transaction
+			electrum_mock.expect(
+				:get_tx_status,
+				{"confirmations" => 1234},
+				["txhash"]
+			)
+			assert_equal 1234, tx.confirmations
+		end
+
+		def test_amount_for_empty
+			_, tx = transaction
+			assert_equal 0, tx.amount_for
+		end
+
+		def test_amount_for_address_not_present
+			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			assert_equal 0, tx.amount_for("other_address")
+		end
+
+		def test_amount_for_address_present
+			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			assert_equal 0.00000001, tx.amount_for("address")
+		end
+
+		def test_amount_for_one_of_address_present
+			_, tx = transaction([{"address" => "address", "value_sats" => 1}])
+			assert_equal 0.00000001, tx.amount_for("boop", "address", "lol")
+		end
+	end
+end

test/test_helper.rb 🔗

@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "minitest/autorun"
+require "rantly/minitest_extensions"
+require "webmock/minitest"
+begin
+	require "pry-rescue/minitest"
+	require "pry-reload"
+rescue LoadError
+	# Just helpers for dev, no big deal if missing
+	nil
+end
+
+module Minitest
+	class Test
+		def self.property(m, &block)
+			define_method("test_#{m}") do
+				property_of(&block).check { |args| send(m, *args) }
+			end
+		end
+	end
+end