module Hydra.Logging.AccountingSpec where

import Hydra.Prelude hiding (first, insert)
import Test.Hydra.Prelude hiding (pending)

import Data.Map.Strict qualified as Map
import Hydra.Logging.Monitoring.Accounting qualified as A

spec :: Spec
spec :: Spec
spec = do
  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"counts an input once and preserves its first processing time across retries" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let (Bool
first, Int
_, Accounting Text Int
s1) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0 Word64
1 (Int
42 :: Int) Accounting Text Int
initial
        (Bool
retry, Int
_, Accounting Text Int
s2) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0.5 Word64
1 Int
42 Accounting Text Int
s1
        (Bool
_, Maybe Double
_, [Double]
times, Accounting Text Int
_) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
0.6 SnapshotKey Text
key1 [Int
42] Accounting Text Int
s2
    (Bool
first, Bool
retry, [Double]
times) (Bool, Bool, [Double]) -> (Bool, Bool, [Double]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` (Bool
True, Bool
False, [Double
0.6])

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"counts externally duplicated messages but preserves their transaction's first time" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let (Bool
_, Int
_, Accounting Text Int
s1) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0 Word64
1 (Int
42 :: Int) Accounting Text Int
initial
        (Bool
fresh, Int
_, Accounting Text Int
s2) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0.5 Word64
2 Int
42 Accounting Text Int
s1
    Bool
fresh Bool -> Bool -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Bool
True
    Accounting Text Int -> Map Int Double
forall head txid. Accounting head txid -> Map txid Double
A.timers Accounting Text Int
s2 Map Int Double -> Map Int Double -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Int -> Double -> Map Int Double
forall k a. k -> a -> Map k a
Map.singleton Int
42 Double
0

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"bounds both timer indexes and expires entries without more traffic" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let insert :: A.Accounting Text Int -> Int -> A.Accounting Text Int
        insert :: Accounting Text Int -> Int -> Accounting Text Int
insert Accounting Text Int
st Int
i = let (Bool
_, Int
_, Accounting Text Int
next) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
3 Double
10 (Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
i) (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
i) Int
i Accounting Text Int
st in Accounting Text Int
next
        full :: Accounting Text Int
full = (Accounting Text Int -> Int -> Accounting Text Int)
-> Accounting Text Int -> [Int] -> Accounting Text Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Accounting Text Int -> Int -> Accounting Text Int
insert Accounting Text Int
initial [Int
1 .. Int
100]
        (Int
removed, Accounting Text Int
expired) = Int
-> Double
-> Double
-> Accounting Text Int
-> (Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Accounting head txid
-> (Int, Accounting head txid)
A.prune Int
3 Double
10 Double
111 Accounting Text Int
full
    Map Int Double -> [Int]
forall k a. Map k a -> [k]
Map.keys (Accounting Text Int -> Map Int Double
forall head txid. Accounting head txid -> Map txid Double
A.timers Accounting Text Int
full) [Int] -> [Int] -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` [Int
98, Int
99, Int
100]
    Map (Double, Int) () -> Int
forall k a. Map k a -> Int
Map.size (Accounting Text Int -> Map (Double, Int) ()
forall head txid. Accounting head txid -> Map (Double, txid) ()
A.orderedTimers Accounting Text Int
full) Int -> Int -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Int
3
    Int
removed Int -> Int -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Int
3
    Accounting Text Int -> Map Int Double
forall head txid. Accounting head txid -> Map txid Double
A.timers Accounting Text Int
expired Map Int Double -> Map Int Double -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Map Int Double
forall k a. Map k a
Map.empty
    Accounting Text Int -> Map (Double, Int) ()
forall head txid. Accounting head txid -> Map (Double, txid) ()
A.orderedTimers Accounting Text Int
expired Map (Double, Int) () -> Map (Double, Int) () -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Map (Double, Int) ()
forall k a. Map k a
Map.empty

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"removes invalid or dropped transactions from both indexes" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let (Bool
_, Int
_, Accounting Text Int
pending) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0 Word64
1 (Int
42 :: Int) Accounting Text Int
initial
        next :: Accounting Text Int
next = Int -> Accounting Text Int -> Accounting Text Int
forall txid head.
Ord txid =>
txid -> Accounting head txid -> Accounting head txid
A.forget Int
42 Accounting Text Int
pending
    Accounting Text Int -> Map Int Double
forall head txid. Accounting head txid -> Map txid Double
A.timers Accounting Text Int
next Map Int Double -> Map Int Double -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Map Int Double
forall k a. Map k a
Map.empty
    Accounting Text Int -> Map (Double, Int) ()
forall head txid. Accounting head txid -> Map (Double, txid) ()
A.orderedTimers Accounting Text Int
next Map (Double, Int) () -> Map (Double, Int) () -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Map (Double, Int) ()
forall k a. Map k a
Map.empty

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"does not count repeated adoption or a version bump at the same number" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let (Bool
first, Maybe Double
_, [Double]
_, Accounting Text Int
s1) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
1 SnapshotKey Text
key1 [Int
42] Accounting Text Int
initial
        (Bool
again, Maybe Double
_, [Double]
_, Accounting Text Int
s2) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
2 SnapshotKey Text
key1 [Int
42] Accounting Text Int
s1
        (Bool
bumped, Maybe Double
_, [Double]
_, Accounting Text Int
_) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
3 (Text -> Natural -> Natural -> SnapshotKey Text
forall head. head -> Natural -> Natural -> SnapshotKey head
A.SnapshotKey Text
"head" Natural
1 Natural
1) [Int
42] Accounting Text Int
s2
    (Bool
first, Bool
again, Bool
bumped) (Bool, Bool, Bool) -> (Bool, Bool, Bool) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` (Bool
True, Bool
False, Bool
False)

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"does not consume or time an unrelated in-flight snapshot when adopting an older one" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let pending :: Accounting Text Int
pending = SnapshotKey Text
-> Maybe Double
-> [Int]
-> Accounting Text Int
-> Accounting Text Int
forall head txid.
Eq head =>
SnapshotKey head
-> Maybe Double
-> [txid]
-> Accounting head txid
-> Accounting head txid
A.startSnapshot SnapshotKey Text
key2 (Double -> Maybe Double
forall a. a -> Maybe a
Just Double
1) [Int
43] Accounting Text Int
initial
        (Bool
_, Maybe Double
duration, [Double]
_, Accounting Text Int
next) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
2 SnapshotKey Text
key1 [Int
42] Accounting Text Int
pending
    Maybe Double
duration Maybe Double -> Maybe Double -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Maybe Double
forall a. Maybe a
Nothing
    Accounting Text Int
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall head txid.
Accounting head txid
-> Maybe (SnapshotKey head, Maybe Double, [txid])
A.inFlight Accounting Text Int
next Maybe (SnapshotKey Text, Maybe Double, [Int])
-> Maybe (SnapshotKey Text, Maybe Double, [Int]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` (SnapshotKey Text, Maybe Double, [Int])
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall a. a -> Maybe a
Just (SnapshotKey Text
key2, Double -> Maybe Double
forall a. a -> Maybe a
Just Double
1, [Int
43])

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"does not match the same snapshot number in another head or version" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let pending :: Accounting Text Int
pending = SnapshotKey Text
-> Maybe Double
-> [Int]
-> Accounting Text Int
-> Accounting Text Int
forall head txid.
Eq head =>
SnapshotKey head
-> Maybe Double
-> [txid]
-> Accounting head txid
-> Accounting head txid
A.startSnapshot SnapshotKey Text
key1 (Double -> Maybe Double
forall a. a -> Maybe a
Just Double
1) [Int
42] Accounting Text Int
initial
        (Bool
_, Maybe Double
duration, [Double]
_, Accounting Text Int
next) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
2 (Text -> Natural -> Natural -> SnapshotKey Text
forall head. head -> Natural -> Natural -> SnapshotKey head
A.SnapshotKey Text
"other" Natural
0 Natural
1) [Int
42] Accounting Text Int
pending
    Maybe Double
duration Maybe Double -> Maybe Double -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Maybe Double
forall a. Maybe a
Nothing
    Accounting Text Int
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall head txid.
Accounting head txid
-> Maybe (SnapshotKey head, Maybe Double, [txid])
A.inFlight Accounting Text Int
next Maybe (SnapshotKey Text, Maybe Double, [Int])
-> Maybe (SnapshotKey Text, Maybe Double, [Int]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Accounting Text Int
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall head txid.
Accounting head txid
-> Maybe (SnapshotKey head, Maybe Double, [txid])
A.inFlight Accounting Text Int
pending

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"restores snapshot contents without inventing a duration" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let pending :: Accounting Text Int
pending = SnapshotKey Text
-> Maybe Double
-> [Int]
-> Accounting Text Int
-> Accounting Text Int
forall head txid.
Eq head =>
SnapshotKey head
-> Maybe Double
-> [txid]
-> Accounting head txid
-> Accounting head txid
A.startSnapshot SnapshotKey Text
key2 Maybe Double
forall a. Maybe a
Nothing [Int
43] Accounting Text Int
initial{A.confirmed = Just key1}
        (Bool
advanced, Maybe Double
duration, [Double]
timings, Accounting Text Int
next) = Double
-> SnapshotKey Text
-> [Int]
-> Accounting Text Int
-> (Bool, Maybe Double, [Double], Accounting Text Int)
forall head txid.
(Eq head, Ord txid) =>
Double
-> SnapshotKey head
-> [txid]
-> Accounting head txid
-> (Bool, Maybe Double, [Double], Accounting head txid)
A.confirmSnapshot Double
500 SnapshotKey Text
key2 [Int
43] Accounting Text Int
pending
    (Bool
advanced, Maybe Double
duration, [Double]
timings) (Bool, Maybe Double, [Double])
-> (Bool, Maybe Double, [Double]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` (Bool
True, Maybe Double
forall a. Maybe a
Nothing, [])
    Accounting Text Int
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall head txid.
Accounting head txid
-> Maybe (SnapshotKey head, Maybe Double, [txid])
A.inFlight Accounting Text Int
next Maybe (SnapshotKey Text, Maybe Double, [Int])
-> Maybe (SnapshotKey Text, Maybe Double, [Int]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Maybe (SnapshotKey Text, Maybe Double, [Int])
forall a. Maybe a
Nothing

  String -> Expectation -> SpecWith (Arg Expectation)
forall a.
(HasCallStack, Example a) =>
String -> a -> SpecWith (Arg a)
it String
"clears pending state without forgetting the confirmed watermark" (Expectation -> SpecWith (Arg Expectation))
-> Expectation -> SpecWith (Arg Expectation)
forall a b. (a -> b) -> a -> b
$ do
    let (Bool
_, Int
_, Accounting Text Int
pending) = Int
-> Double
-> Double
-> Word64
-> Int
-> Accounting Text Int
-> (Bool, Int, Accounting Text Int)
forall txid head.
Ord txid =>
Int
-> Double
-> Double
-> Word64
-> txid
-> Accounting head txid
-> (Bool, Int, Accounting head txid)
A.request Int
10 Double
3600 Double
0 Word64
1 Int
42 Accounting Text Int
initial{A.confirmed = Just key1}
        cleared :: Accounting Text Int
cleared = Accounting Text Int -> Accounting Text Int
forall head txid. Accounting head txid -> Accounting head txid
A.clearPending (Accounting Text Int -> Accounting Text Int)
-> Accounting Text Int -> Accounting Text Int
forall a b. (a -> b) -> a -> b
$ SnapshotKey Text
-> Maybe Double
-> [Int]
-> Accounting Text Int
-> Accounting Text Int
forall head txid.
Eq head =>
SnapshotKey head
-> Maybe Double
-> [txid]
-> Accounting head txid
-> Accounting head txid
A.startSnapshot SnapshotKey Text
key2 (Double -> Maybe Double
forall a. a -> Maybe a
Just Double
0) [Int
42] Accounting Text Int
pending
    Accounting Text Int -> Maybe (SnapshotKey Text)
forall head txid. Accounting head txid -> Maybe (SnapshotKey head)
A.confirmed Accounting Text Int
cleared Maybe (SnapshotKey Text) -> Maybe (SnapshotKey Text) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` SnapshotKey Text -> Maybe (SnapshotKey Text)
forall a. a -> Maybe a
Just SnapshotKey Text
key1
    Accounting Text Int
-> Maybe (SnapshotKey Text, Maybe Double, [Int])
forall head txid.
Accounting head txid
-> Maybe (SnapshotKey head, Maybe Double, [txid])
A.inFlight Accounting Text Int
cleared Maybe (SnapshotKey Text, Maybe Double, [Int])
-> Maybe (SnapshotKey Text, Maybe Double, [Int]) -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Maybe (SnapshotKey Text, Maybe Double, [Int])
forall a. Maybe a
Nothing
    Map Int Double -> Int
forall k a. Map k a -> Int
Map.size (Accounting Text Int -> Map Int Double
forall head txid. Accounting head txid -> Map txid Double
A.timers Accounting Text Int
cleared) Int -> Int -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Int
0
    Map (Double, Int) () -> Int
forall k a. Map k a -> Int
Map.size (Accounting Text Int -> Map (Double, Int) ()
forall head txid. Accounting head txid -> Map (Double, txid) ()
A.orderedTimers Accounting Text Int
cleared) Int -> Int -> Expectation
forall a. (HasCallStack, Show a, Eq a) => a -> a -> Expectation
`shouldBe` Int
0
 where
  initial :: A.Accounting Text Int
  initial :: Accounting Text Int
initial = Accounting Text Int
forall head txid. Accounting head txid
A.empty
  key1 :: SnapshotKey Text
key1 = Text -> Natural -> Natural -> SnapshotKey Text
forall head. head -> Natural -> Natural -> SnapshotKey head
A.SnapshotKey Text
"head" Natural
0 Natural
1
  key2 :: SnapshotKey Text
key2 = Text -> Natural -> Natural -> SnapshotKey Text
forall head. head -> Natural -> Natural -> SnapshotKey head
A.SnapshotKey Text
"head" Natural
0 Natural
2