Search code examples
ruby-on-railsruby-on-rails-4integration-testingbitcoind

Rails 4: how to make request from outside of Rails app during integration test?


I have an application that needs 2-way communication to external daemon (bitcoind). There is functionality in bitcoind that allows to call my application whenever new block or transaction of interest occurs ('--walletnotify' and '--blocknotify'). For that I'm using CURL to request "http://myapp/walletnotify" and so on:

walletnotify = /usr/bin/curl --max-time 60 http://myapp/walletnotify/%s

I'm trying to create integration tests for this callback behavior. Unfortunately when running integration tests, I'm receiving errors on daemon side, as it is not able to perform requests to "http://myapp/walletnotify" - obviously Rails server cannot be reached (or the connection is interrupted?). Of course tests fail as appropriate actions are not called.

My question is: how to properly test such scenario? Is there any way to allow for direct external requests to application during integration tests? Is there a way to make sure that Rails server is running during integration tests? Or maybe I should listen to such requests inside integration test and then proxy them to application?

Update 2018-06-03: I'm using minitest. The test that I'm trying to run is here:

https://github.com/cryptogopher/token_voting/blob/master/test/integration/token_votes_notify_test.rb

After calling

@rpc.generate(101)

bitcoind daemon in regtest mode should generate 101 blocks and call 'blocknotify' callbacks. The problem is it cannot send HTTP request to application during test.


Solution

  • Ok, I resolved this one.

    Looks like 'minitest' does not start application server no matter which driver you choose. Still you can start your own HTTP server to listen for notifications from external sources and forward them to tested application.

    For this to work you need:

    require 'webrick'
    

    Setup HTTP server (logging disabled to avoid clutter):

    server = WEBrick::HTTPServer.new(
      Port: 3000,
      Logger: WEBrick::Log.new("/dev/null"),
      AccessLog: []
    )
    

    Specify how to handle incoming HTTP requests. In my case there will be only GET requests, but it is important to forward them with original headers:

    server.mount_proc '/' do |req, resp|
      headers = {}
      req.header.each { |k,v| v.each { |a| headers[k] = a } }
      resp = get req.path, {}, headers
    end
    

    Start HTTP server in separate thread (it is blocking the thread where it is running):

    @t = Thread.new {
      server.start
    }
    Minitest.after_run do
      @t.kill
      @t.join
    end
    Timeout.timeout(5) do
      sleep 0.1 until server.status == :Running
    end