Wednesday, July 15, 2026 3:56:07 AM
> project show esm_bot
A sophisticated Discord bot that bridges Arma 3 Exile servers with Discord communities. Built in Ruby with secure TCP communication, it delivers territory management, real-time notifications, and comprehensive server administration right in your Discord channels.
Details
> server_spec.rb
# frozen_string_literal: true

RSpec.describe ESM::Website::API::Server, :integration do
  let(:secret) { SecureRandom.hex(32) }
  let(:subject_prefix) { "esm.bot.rpc." }
  let(:broker) { Spec::NatsBroker.new.start }
  let(:client) { NATS.connect(broker.url) }
  let(:handler) { ESM::Website::API::Handlers::Ping }
  let(:server) { described_class.new.register(:ping, handler) }

  let(:build_envelope) do
    ->(action:, payload:) {
      body = {action:, payload:, issued_at: Time.now.to_i, nonce: SecureRandom.uuid}.to_json
      {body:, signature: OpenSSL::HMAC.hexdigest("SHA256", secret, body)}
    }
  end

  let!(:original_url) { Settings.nats.url }
  let!(:original_secret) { Settings.nats.shared_secret }

  before do
    Settings.nats.url = broker.url
    Settings.nats.shared_secret = secret
    server.start
  end

  after do
    server.stop
    client.close
    broker.stop
    Settings.nats.url = original_url
    Settings.nats.shared_secret = original_secret
  end

  describe "round-trip" do
    it "returns the handler result inside an ok envelope" do
      envelope = build_envelope.call(action: "ping", payload: {hello: "world"})

      response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
      parsed = JSON.parse(response.data, symbolize_names: true)

      expect(parsed[:ok]).to be true
      expect(parsed.dig(:result, :echo, :hello)).to eq("world")
      expect(parsed.dig(:result, :server_time)).to be_a(String)
    end
  end

  describe "deferred handler" do
    context "when the handler offloads to a promise that resolves" do
      let(:handler) do
        Class.new do
          def self.call(**payload)
            Concurrent::Promise.execute { {deferred: payload[:value]} }
          end
        end
      end

      it "replies with the resolved value once the promise settles" do
        envelope = build_envelope.call(action: "ping", payload: {value: 42})

        response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
        parsed = JSON.parse(response.data, symbolize_names: true)

        expect(parsed[:ok]).to be true
        expect(parsed.dig(:result, :deferred)).to eq(42)
      end
    end

    context "when the handler's promise rejects" do
      let(:handler) do
        Class.new do
          def self.call(**)
            Concurrent::Promise.execute { raise StandardError, "secret background detail" }
          end
        end
      end

      it "replies with a redacted error once the promise rejects" do
        envelope = build_envelope.call(action: "ping", payload: {})

        response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
        parsed = JSON.parse(response.data, symbolize_names: true)

        expect(parsed[:ok]).to be false
        expect(parsed[:error]).to eq("unknown")
        expect(parsed[:detail]).not_to include("secret background detail")
      end
    end
  end

  describe "envelope shape" do
    it "rejects a body that's valid JSON but not a Hash" do
      body = "null"
      envelope = {
        body: body,
        signature: OpenSSL::HMAC.hexdigest("SHA256", secret, body)
      }

      response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
      parsed = JSON.parse(response.data, symbolize_names: true)

      expect(parsed[:ok]).to be false
      expect(parsed[:error]).to eq("invalid_envelope")
    end

    it "rejects an envelope whose issued_at is older than the staleness window" do
      stale_body = {
        action: "ping",
        payload: {},
        issued_at: Time.now.to_i - (10 * 60),
        nonce: SecureRandom.uuid
      }.to_json
      envelope = {
        body: stale_body,
        signature: OpenSSL::HMAC.hexdigest("SHA256", secret, stale_body)
      }

      response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
      parsed = JSON.parse(response.data, symbolize_names: true)

      expect(parsed[:ok]).to be false
      expect(parsed[:error]).to eq("invalid_envelope")
    end
  end

  describe "internal error redaction" do
    let(:handler) { class_double(ESM::Website::API::Handlers::Ping) }

    before { allow(handler).to receive(:call).and_raise(StandardError, "secret internal detail with table=communities sql=...") }

    it "returns a generic detail string for :unknown errors, not the exception message" do
      envelope = build_envelope.call(action: "ping", payload: {})
      response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 5)
      parsed = JSON.parse(response.data, symbolize_names: true)

      expect(parsed[:ok]).to be false
      expect(parsed[:error]).to eq("unknown")
      expect(parsed[:detail]).not_to include("secret internal detail")
      expect(parsed[:detail]).not_to include("communities")
    end
  end

  describe "HMAC verification" do
    let(:handler) { class_double(ESM::Website::API::Handlers::Ping, call: {echo: {}}) }

    it "rejects an envelope with a bad signature without invoking the handler" do
      envelope = build_envelope.call(action: "ping", payload: {hello: "world"})
      tampered = envelope.merge(signature: "deadbeef" * 8)

      response = client.request("#{subject_prefix}ping", tampered.to_json, timeout: 5)
      parsed = JSON.parse(response.data, symbolize_names: true)

      expect(parsed[:ok]).to be false
      expect(parsed[:error]).to eq("signature_invalid")
      expect(handler).not_to have_received(:call)
    end
  end

  describe "broker reconnect" do
    let(:client) { NATS.connect(broker.url, reconnect_time_wait: 0.1, max_reconnect_attempts: 50) }

    it "re-establishes the subscription after the broker restarts" do
      first = client.request(
        "#{subject_prefix}ping",
        build_envelope.call(action: "ping", payload: {phase: "before"}).to_json,
        timeout: 5
      )
      expect(JSON.parse(first.data, symbolize_names: true)[:ok]).to be true

      broker.restart

      # The server's @nats client uses default reconnect_time_wait (2s), so poll
      # for a successful response rather than guessing the right sleep.
      parsed = nil
      deadline = Time.now + 10
      loop do
        envelope = build_envelope.call(action: "ping", payload: {phase: "after"})
        response = client.request("#{subject_prefix}ping", envelope.to_json, timeout: 2)
        parsed = JSON.parse(response.data, symbolize_names: true)
        break if parsed[:ok]
        raise "broker reconnect did not re-establish subscription within 10s" if Time.now > deadline
      rescue NATS::IO::NoRespondersError, NATS::IO::Timeout
        raise if Time.now > deadline
        sleep 0.2
        retry
      end

      expect(parsed[:ok]).to be true
      expect(parsed.dig(:result, :echo, :phase)).to eq("after")
    end
  end
end
All opinions represented herein are my own
- © 2024 - 2026 itsthedevman
- build 43aa0d7