coffer/tests/test_people.py

192 lines
9.5 KiB
Python

import tempfile
import unittest
from pathlib import Path
from coffer.ledger import InsufficientCredit, TransferNotAllowed
from coffer.store import NotAReviewer, OrderNotPending, TooManyPendingOrders
from coffer.product import CofferProduct, ProductError
from coffer.public_ledger import tx_public
from coffer.units import human_account
class PeopleTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db = Path(self.tmp.name) / "coffer.db"
self.p = CofferProduct(self.db)
self.p.bootstrap_opening_books()
self.p.record_build_contract()
self.p.signup_flow("atlas") # an agent: 40 granted, 20 seat charged
def tearDown(self):
self.p.close()
self.tmp.cleanup()
def test_human_signup_grant_once(self):
r = self.p.human_signup("sam", 101)
self.assertEqual(r["account_id"], "human:sam")
self.assertEqual(r["balance"]["promotional"], 40.0)
self.assertTrue(self.p.human_signup("sam", 101)["already"])
self.assertEqual(self.p.balance("human:sam")["spendable"], 40.0)
with self.assertRaises(ValueError):
human_account("Sam Smith <sam@example.com>")
def test_grant_keyed_on_gitea_id_rename_and_concurrency(self):
self.p.human_signup("sam", 101)
with self.assertRaises(ProductError): # rename: the Gitea id already had its grant
self.p.human_signup("sam-renamed", 101)
self.assertEqual(self.p.balance("human:sam-renamed")["spendable"], 0.0)
import threading
results, errors = [], []
def claim():
p = CofferProduct(self.db) # separate connection, like separate platform requests
try:
results.append(p.human_signup("ana", 555)["already"])
except Exception as exc: # noqa: BLE001
errors.append(exc)
finally:
p.close()
threads = [threading.Thread(target=claim) for _ in range(8)]
[t.start() for t in threads]
[t.join() for t in threads]
self.p.close()
self.p = CofferProduct(self.db)
self.assertEqual(self.p.balance("human:ana")["spendable"], 40.0)
self.assertEqual(results.count(False), 1, (results, errors))
def test_agent_only_entry_points_reject_people(self):
self.p.human_signup("sam", 101)
for fn in (lambda: self.p.grant_signup("human:sam"), lambda: self.p.top_up("human:sam", 5),
lambda: self.p.charge_subscription("human:sam"), lambda: self.p.signup_flow("human:sam")):
with self.assertRaises(ValueError):
fn()
self.assertEqual(self.p.balance("human:sam")["spendable"], 40.0)
def test_payment_ref_bound_to_request(self):
self.p.human_signup("sam", 101)
self.p.human_signup("kim", 102)
self.p.pay("human:sam", "agent:atlas", 2, reason="x", ref="of-r")
for args in (("human:kim", "agent:atlas", 2), ("human:sam", "human:kim", 2), ("human:sam", "agent:atlas", 3)):
with self.assertRaises(ProductError):
self.p.pay(*args, reason="x", ref="of-r")
for bad in (float("nan"), float("inf"), "5", True):
with self.assertRaises(ProductError):
self.p.pay("human:sam", "agent:atlas", bad, reason="x", ref="of-bad")
def test_no_purchase_api(self):
self.assertFalse(hasattr(self.p, "buy_credits"))
def test_human_pays_agent_with_granted_credit(self):
self.p.human_signup("sam", 101)
before = self.p.balance("agent:atlas")
r = self.p.pay("human:sam", "agent:atlas", 12.5, reason="Research brief", ref="of-1")
self.assertEqual(r["tx"]["kind"], "payment")
self.assertEqual(self.p.balance("human:sam")["spendable"], 27.5)
after = self.p.balance("agent:atlas")
self.assertAlmostEqual(after["promotional"] - before["promotional"], 12.5) # kind preserved
self.assertTrue(self.p.pay("human:sam", "agent:atlas", 12.5, reason="x", ref="of-1")["duplicate"])
self.p.close()
self.p = CofferProduct(self.db) # survives a reload
self.assertEqual(self.p.balance("human:sam")["spendable"], 27.5)
self.assertTrue(self.p.pay("human:sam", "agent:atlas", 12.5, reason="x", ref="of-1")["duplicate"])
def test_agent_pays_human_and_people_pay_each_other(self):
self.p.human_signup("sam", 101)
self.p.human_signup("kim", 102)
self.p.pay("agent:atlas", "human:sam", 5, reason="bounty", ref="of-2")
self.assertEqual(self.p.balance("human:sam")["spendable"], 45.0)
self.p.pay("human:sam", "human:kim", 10, reason="thanks", ref="of-3")
self.assertEqual(self.p.balance("human:kim")["spendable"], 50.0)
self.assertEqual(self.p.balance("human:sam")["spendable"], 35.0)
def test_rules(self):
self.p.human_signup("sam", 101)
self.p.signup_flow("bolt")
with self.assertRaises(TransferNotAllowed):
self.p.pay("agent:atlas", "agent:bolt", 1, reason="x", ref="of-4")
with self.assertRaises(TransferNotAllowed):
self.p.pay("human:sam", "company:coffer", 1, reason="x", ref="of-5")
with self.assertRaises(ProductError):
self.p.pay("human:sam", "agent:atlas", 5000, reason="x", ref="of-6")
with self.assertRaises(InsufficientCredit):
self.p.pay("human:sam", "agent:atlas", 41, reason="x", ref="of-7")
# ----- shirt orders: any credit, held until the operator reviews the order
def test_person_orders_with_granted_credit_and_is_rejected_then_released(self):
self.p.human_signup("sam", 101)
o = self.p.buy_shirt("human:sam")
oid = o["order"]["order_id"]
self.assertEqual((o["order"]["status"], o["order"]["status_label"]), ("pending_review", "pending review"))
self.assertEqual((o["balance"]["spendable"], o["balance"]["held"]), (20.0, 20.0))
self.assertEqual([x["order_id"] for x in self.p.pending_orders()], [oid])
with self.assertRaises(NotAReviewer):
self.p.reject_order(oid, "human:sam")
with self.assertRaises(NotAReviewer):
self.p.accept_order(oid, "agent:atlas")
r = self.p.reject_order(oid, "agent:leora", "out of stock this week")
self.assertEqual(r["order"]["status"], "rejected")
b = self.p.balance("human:sam")
self.assertEqual((b["spendable"], b["granted"], b["held"]), (40.0, 40.0, 0.0)) # released in full, same kind
with self.assertRaises(OrderNotPending):
self.p.accept_order(oid, "agent:leora")
self.assertEqual(self.p.pending_orders(), [])
kinds = [t["kind"] for t in self.p.history()]
self.assertIn("order_hold", kinds)
self.assertIn("order_release", kinds)
def test_pending_order_limit_and_insufficient_credit(self):
self.p.human_signup("sam", 101)
self.p.buy_shirt("human:sam")
self.p.buy_shirt("human:sam")
with self.assertRaises(TooManyPendingOrders):
self.p.buy_shirt("human:sam")
self.assertEqual(self.p.balance("human:sam")["held"], 40.0)
self.p.human_signup("kai", 102)
with self.assertRaises(InsufficientCredit):
self.p.buy_shirt("human:kai", quantity=3) # 60c > 40c
self.assertEqual(self.p.balance("human:kai")["spendable"], 40.0)
first = self.p.pending_orders()[0]["order_id"]
self.p.accept_order(first, "agent:leora")
with self.assertRaises(InsufficientCredit): # the slot is free again; the credit is spent
self.p.buy_shirt("human:sam")
def test_accept_charges_hold_and_is_public(self):
o = self.p.buy_shirt("atlas")
a = self.p.accept_order(o["order"]["order_id"], "agent:leora", "ok")
self.assertEqual((a["order"]["status"], a["order"]["credits_charged"], a["order"]["reviewed_by"]), ("procuring", 20, "agent:leora"))
snap = self.p.public_snapshot()
self.assertTrue(any(t["kind"] == "purchase" for t in snap["transactions"]))
pending_file = Path(self.db).with_name("pending-orders.json")
self.assertTrue(pending_file.exists())
import json
self.assertEqual(json.loads(pending_file.read_text())["pending"], [])
def test_orders_survive_restart(self):
o = self.p.buy_shirt("atlas")
self.p.close()
self.p = CofferProduct(self.db)
self.assertEqual(self.p.pending_orders()[0]["order_id"], o["order"]["order_id"])
self.assertEqual(self.p.balance("atlas")["held"], 20.0)
self.p.reject_order(o["order"]["order_id"], "agent:leora")
self.assertEqual(self.p.balance("atlas")["spendable"], 20.0)
def test_grants_spent_first_and_tracked_apart(self):
self.p.human_signup("sam", 101)
self.p.top_up("atlas", 30) # atlas: 20 granted + 30 funded
self.p.pay("atlas", "human:sam", 25, reason="refund for a service", ref="atlas-sam-1")
a = self.p.balance("atlas")
self.assertEqual((a["granted"], a["funded"]), (0.0, 25.0))
sam = self.p.balance("human:sam")
self.assertEqual((sam["granted"], sam["earned"]), (60.0, 5.0))
def test_public_ledger_shows_handles_only(self):
self.p.human_signup("sam", 101)
self.p.pay("human:sam", "agent:atlas", 2, reason="Review", ref="of-8")
pub = [tx_public(t) for t in self.p.history()]
self.assertIn("human:sam", repr(pub))
self.assertEqual(next(t for t in pub if t["kind"] == "payment")["meta"].get("offer"), "of-8")
if __name__ == "__main__":
unittest.main()