import unittest,tempfile,os,threading
from core import Engine,DomainError
class InventoryTest(unittest.TestCase):
 def setUp(self):
  f,self.path=tempfile.mkstemp();os.close(f);self.e=Engine(self.path);self.e.seed();self.e.apply('product',{'sku':'P1','name':'Test','stock':10,'safety':2,'price':5,'reorder':3});self.p=self.e.rows('products')[0]['id']
  for store in ['amazon-01','ebay-01']:self.e.apply('mapping',{'product':self.p,'store':store,'listing_sku':'REMOTE-P1'})
 def tearDown(self):self.e.close();os.unlink(self.path)
 def order(self,ref='O1',q=3,store='amazon-01'):return {'external_id':ref,'store':store,'items':[{'product':self.p,'qty':q}]}
 def test_deduction_and_fanout(self):
  self.e.apply('order',self.order());self.assertEqual(self.e.rows('products')[0]['stock'],7);self.assertEqual(len(self.e.rows('jobs')),2)
 def test_duplicate_no_deduction(self):
  self.e.apply('order',self.order());r=self.e.apply('order',self.order());self.assertTrue(r['duplicate']);self.assertEqual(self.e.rows('products')[0]['stock'],7)
 def test_cancel_once(self):
  self.e.apply('order',self.order());oid=self.e.rows('orders')[0]['id'];self.e.apply('cancel',{'id':oid});self.e.apply('cancel',{'id':oid});self.assertEqual(self.e.rows('products')[0]['stock'],10)
 def test_insufficient_atomic(self):
  with self.assertRaises(DomainError):self.e.apply('order',self.order(q=11))
  self.assertEqual(len(self.e.rows('orders')),0);self.assertEqual(self.e.rows('products')[0]['stock'],10)
 def test_duplicate_items_aggregate(self):
  o=self.order(q=6);o['items']*=2
  with self.assertRaises(DomainError):self.e.apply('order',o)
  self.assertEqual(self.e.rows('products')[0]['stock'],10)
 def test_stores_have_separate_order_ids(self):
  self.e.apply('order',self.order(q=1));self.e.apply('order',self.order(q=1,store='ebay-01'));self.assertEqual(self.e.rows('products')[0]['stock'],8)
 def test_negative_adjust_rejected(self):
  with self.assertRaises(DomainError):self.e.apply('adjust',{'product':self.p,'delta':-20,'reason':'test'})
 def test_concurrent_sales(self):
  results=[]
  def sell(i):
   e=Engine(self.path)
   try:e.apply('order',self.order(ref=str(i),q=7));results.append('ok')
   except DomainError:results.append('rejected')
   finally:e.close()
  ts=[threading.Thread(target=sell,args=(i,)) for i in range(2)]
  for t in ts:t.start()
  for t in ts:t.join()
  self.assertCountEqual(results,['ok','rejected']);self.assertEqual(self.e.rows('products')[0]['stock'],3)
 def test_ship_cannot_cancel(self):
  self.e.apply('order',self.order());oid=self.e.rows('orders')[0]['id'];self.e.apply('ship',{'id':oid,'tracking':'TEST'})
  with self.assertRaises(DomainError):self.e.apply('cancel',{'id':oid})
if __name__=='__main__':unittest.main()
