DpExchange.Robinhood.Fake (DpExchangeRobinhood v0.3.2)

Copy Markdown View Source

An in-process Robinhood, for a consumer's tier-1 tests and for the conformance suite.

It is not a mock. A real implementation of DpExchange.Core.Venue answering from memory, running the same conformance suite as the real adapter.

What it models that is specific to this venue

  • Credentials are required for every signed call, because the real venue signs every request — including market data — and has no anonymous endpoint at all. Without them: {:error, {:missing_credentials, :robinhood}}, the exact shape DpExchange.Robinhood.Auth.headers/5 returns for a request that never reaches the venue. Not {:refused, ...} — a refusal is the venue's own permanent word about a request it received (see DpExchange.Core.Venue's moduledoc on the two); a missing local credential is never sent at all, and is :error for the same reason Auth.headers/5 is. Checked by every function below that reaches a real endpoint on this venue, market data and trading alike — there is no venue-internal function this venue leaves ungated, because there is no venue-internal function the real venue leaves unsigned.
  • Coverage is :internal_poll, not :stream — the one place a consumer can see that this venue has no socket, and it shows up as what is arriving, never as how.
  • No candles, no order book, no volume. The venue serves none, so neither does this.
  • subscribe/2 takes no :to. The real venue's DpExchange.Core.Venue.subscribe/2 has no notion of a per-call recipient — data reaches whoever the feed was supervised with, fixed at boot — so this fake ignores it too and always delivers to the calling process, the same way a caller of the real facade receives from whichever process it supervised the feed under. subscribe_notices/2 is the one call on this venue that legitimately takes :toDpExchange.Robinhood.Feed's own per-call notice registry — and this fake honours it there, correctly.

Failure injection and anonymous mode

Every function below that has a real success path (not an unconditional Venue.not_supported()) checks DpExchange.Core.FakeInjection.next_outcome/1 or /2 first — a queued or always-set outcome from FakeInjection.queue_failures/2,3 or fail_always/2,3 short-circuits the fake's normal logic and is returned as-is. authenticated/1 also checks FakeInjection.credentials_bypassed?/1 before its normal {:error, {:missing_credentials, :robinhood}} path. Neither changes anything for a test that never calls FakeInjection — see that module for the full contract.

subscribe/2, unsubscribe/2 and update_symbols/2 are NOT wired: each takes a list of symbols in one call, and "this one symbol in the batch fails, the rest succeed" is a case whole-call injection cannot express — see FakeInjection's own moduledoc. subscribe_notices/1 IS wired, unlike those three: it takes no symbol list, so a queued or always-set outcome (for example {:error, :feed_not_started}, which the real facade answers when its feed is not running) applies to the whole call the same way it does for get_symbols/1 or market_status/1.

Summary

Functions

coverage/1, split by kind. Same single-key shape as the real venue, and for the same reason: get_top_of_book/2 above is this fake's only source of subscription data and it produces exclusively Types.TopOfBook, so :top_of_book is the only kind there is to report — see DpExchange.Robinhood.coverage_by_kind/1 for why the family requires this callback even where a venue has nothing to split.

Registers opts[:to] for this venue's own notices. Unlike subscribe/2, :to is genuine here — see this module's moduledoc.

Functions

coverage_by_kind(opts \\ [])

@spec coverage_by_kind(keyword()) :: %{
  required(DpExchange.Core.Capabilities.data_kind()) => %{
    required(DpExchange.Core.Venue.symbol()) => DpExchange.Core.Venue.route()
  }
}

coverage/1, split by kind. Same single-key shape as the real venue, and for the same reason: get_top_of_book/2 above is this fake's only source of subscription data and it produces exclusively Types.TopOfBook, so :top_of_book is the only kind there is to report — see DpExchange.Robinhood.coverage_by_kind/1 for why the family requires this callback even where a venue has nothing to split.

subscribe_notices(opts \\ [])

Registers opts[:to] for this venue's own notices. Unlike subscribe/2, :to is genuine here — see this module's moduledoc.

Routed through with_injection/2, unlike subscribe/2, unsubscribe/2 and update_symbols/2: this call carries no symbol list, so there is no "one symbol in the batch" case for whole-call injection to fail at. That is what makes {:error, :feed_not_started} — the real facade's answer when its feed is not running — reachable here at all: FakeInjection.fail_always(:robinhood, {:error, :feed_not_started}) or queue_failures/2,3 produces it, exactly as they produce any other queued outcome. With nothing queued, this fake has no feed of its own to be down, so it answers :ok.