AuroraMeter. StorageCase
(Aurora Meter v1.0.0)
View Source
The conformance suite every AuroraMeter.Storage adapter should pass.
Aurora Meter ships one adapter, AuroraMeter.Storage.Ecto. Writing another
one is supported, and this is how you find out whether yours is correct
before it is asked to hold money. use it inside an ExUnit case that already
knows how to reach your storage:
defmodule MyApp.RiakStorageTest do
use ExUnit.Case, async: false
use AuroraMeter.StorageCase, adapter: MyApp.RiakStorage
endIt installs your adapter as config :aurora_meter, :storage for the
duration of each test and restores whatever was there before, then drives it
through AuroraMeter.Storage's own dispatchers rather than calling your
module directly: what a host observes is what is asserted.
Options
:adapter(required) the module under test.:checkout{module, function}called with no arguments insetup, for an adapter that needs a connection checked out first. The Ecto adapter under the SQL sandbox does; an in-memory one will not.:tenant_prefixa string the generated tenant keys start with, so your own cleanup can find them. Default"storage_case".
What it asserts
An adapter that declares :durable_events must record, deduplicate by
identity, refuse a changed payload under an identity it already holds, keep
its totals arithmetic exact, and not project a duplicate a second time. An
adapter that declares nothing must answer {:error, {:unsupported, _}} from
every durable dispatcher. Both pass this suite: declining the work is a
supported answer, and the difference between declining it and faking it is
the difference between a caller that can handle the situation and one that
cannot.
Each assertion lives in a public function of this module and the generated
test blocks are one line each, so a failure names the function you can read
rather than a line inside a macro expansion.
It does not assert anything about counters or history: those callbacks
predate this suite and the existing adapter tests cover them. It does assert
AuroraMeter.Storage.list_subscriptions/2, which does not predate it: a
keyset page that repeats or skips a row is a subscription billed twice or not
at all, and that is not something an adapter author should have to discover
in production.
Summary
Functions
The projection generation reads currently resolve to.
The capability vocabulary an adapter may draw from.
One AuroraMeter.Storage.correction_entry/0 for the suite.
One AuroraMeter.Storage.event_entry/0 for the suite, already canonical.
The projected total for one generation, %{quantity: 0, events: 0} when the
row is absent, without going through the active-generation indirection.
Installs the adapter for one test and returns the context the assertions take.
Puts the projection state back to one active generation with nothing being built, for a suite that announced one.
Functions
@spec active_generation() :: non_neg_integer()
The projection generation reads currently resolve to.
The suite restores it after the generation test, because it is one row for the whole installation.
@spec capabilities() :: [AuroraMeter.Storage.capability()]
The capability vocabulary an adapter may draw from.
@spec correction(map(), String.t(), String.t(), pos_integer() | :remaining) :: AuroraMeter.Storage.correction_entry()
One AuroraMeter.Storage.correction_entry/0 for the suite.
There is no payload_hash here on purpose: a correction's canonical tuple
carries the original's feature, occurrence instant and dimensions, so only an
adapter that has read the original can compute it. An adapter that invents
its own definition of "the same correction" fails the duplicate and conflict
assertions above.
@spec entry(map(), String.t(), pos_integer()) :: AuroraMeter.Storage.event_entry()
One AuroraMeter.Storage.event_entry/0 for the suite, already canonical.
The payload_hash is computed with AuroraMeter.Events.Canonical, which is
what the facade does, so an adapter cannot pass by inventing its own
definition of "the same payload".
The projected total for one generation, %{quantity: 0, events: 0} when the
row is absent, without going through the active-generation indirection.
Installs the adapter for one test and returns the context the assertions take.
Restores the previous :storage configuration in on_exit, including
deleting the key when it was absent.
@spec restore_projection!(non_neg_integer()) :: :ok
Puts the projection state back to one active generation with nothing being built, for a suite that announced one.