I would go for Design by Contract. So we can define the rulesets outside of the function and just test against the contracts. Just less noise (not really the question you asked though)
Or in case of testing, i personally do generative testing (that's how I call it) haskell calls it book.realworldhaskell.org/read/testing-and-qualit…
also not really the question you asked.
it's more of an integration test than a unitest unless you test the findThing contracts by I -> O if O can be N variants of O for example. so if the thing accessed for behaviour below is an abstraction that gets injected it could make sense to test our membrane / intent.
Besides that it's an blackbox test where we just check contracts and the behaviour of our blackbox in combination of our intent.
But I guess you already thought this through. To me this is a curious questions as well. I wrote an ORM and since an ORM is very very abstract the abstraction tests are very exhaustive and without complete inversion of control to some degree questionable since they IO can have a multitude of side effects and testing derived state against an intent of state is a pain in the ass to maintain (simple put to what i mean -> using static outcome will make you have a n^k variance since all types of states have to be tested which is easy in simple cases but as soon as we got the factors combined testing against all of this getting harder
I talked with a friend of mine and he's a fan of upper / lower boundary tests in such cases .... again I am rambling :) I have no real answer since this is a lot about design and usecase and accessibility and complexity.
But I love to hear you or other conclusions to this topic :).