test_models.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import datetime
  2. from django.test import TestCase
  3. from django.core.exceptions import ValidationError
  4. from ..models import (
  5. Cost, CostUse, Document, Good, GoodUse, Service, ServiceUse)
  6. THREE_YEARS = datetime.timedelta(days=365*3)
  7. class ServiceTests(TestCase):
  8. def setUp(self):
  9. self.doc = Document.objects.create(name='budget')
  10. self.electricity_cost = Cost.objects.create(
  11. name='electricity',
  12. price=10,
  13. document=self.doc,
  14. )
  15. self.server = Good.objects.create(
  16. name="Computer",
  17. price=10,
  18. document=self.doc,
  19. provisioning_duration=THREE_YEARS,
  20. )
  21. def test_get_prices_zero(self):
  22. s = Service.objects.create(name='Foo', document=self.doc)
  23. self.assertEqual(s.get_prices(), {
  24. 'total_recurring_price': 0,
  25. 'total_goods_value_share': 0,
  26. 'unit_recurring_price': 0,
  27. 'unit_goods_value_share': 0,
  28. 'unit_recurring_price': 0,
  29. 'unit_consolidated_cost': 0,
  30. 'unit_staggered_goods_share': 0,
  31. })
  32. def test_get_prices_w_costs(self):
  33. s = Service.objects.create(name='Foo', document=self.doc)
  34. CostUse.objects.create(
  35. service=s, resource=self.electricity_cost, share=0.4)
  36. self.assertEqual(s.get_prices(), {
  37. 'total_recurring_price': 10,
  38. 'unit_recurring_price': 0,
  39. 'unit_goods_value_share': 0,
  40. 'total_goods_value_share': 0,
  41. 'unit_staggered_goods_share': 0.0,
  42. 'unit_consolidated_cost': 0,
  43. })
  44. s.subscriptions_count = 2
  45. s.save()
  46. self.assertEqual(s.get_prices(), {
  47. 'total_recurring_price': 10,
  48. 'total_goods_value_share': 0,
  49. 'unit_recurring_price': 5,
  50. 'unit_goods_value_share': 0,
  51. 'unit_staggered_goods_share': 0,
  52. 'unit_consolidated_cost': 5.0,
  53. })
  54. def test_get_prices_w_goods(self):
  55. s = Service.objects.create(
  56. name='Foo', document=self.doc, subscriptions_count=0)
  57. GoodUse.objects.create(
  58. service=s, resource=self.server, share=0.4)
  59. self.assertEqual(s.get_prices(), {
  60. 'total_recurring_price': 10/(365*3)*365.25/12,
  61. 'unit_recurring_price': 0,
  62. 'unit_goods_value_share': 0,
  63. 'total_goods_value_share': 10,
  64. 'unit_consolidated_cost': 0.0,
  65. 'unit_staggered_goods_share': 0.0,
  66. })
  67. s.subscriptions_count = 2
  68. s.save()
  69. total_recurring_price = 10/(365*3)*365.25/12
  70. self.assertEqual(s.get_prices(), {
  71. 'total_recurring_price': total_recurring_price,
  72. 'total_goods_value_share': 10,
  73. 'unit_recurring_price': total_recurring_price/2,
  74. 'unit_goods_value_share': 5,
  75. 'unit_staggered_goods_share': 5/36,
  76. 'unit_consolidated_cost': total_recurring_price/2 + 5/36,
  77. })
  78. def test_compare_service(self):
  79. s_first = Service.objects.create(
  80. name='Foo', document=self.doc, subscriptions_count=3)
  81. # The first service has no cost/goods associated
  82. s_second = Service.objects.create(
  83. name='Bar', document=self.doc, subscriptions_count=2)
  84. GoodUse.objects.create(
  85. service=s_second, resource=self.server, share=0.4)
  86. total_recurring_price = 10/(365*3)*365.25/12
  87. self.assertEqual(s_second.compare(s_first), {
  88. 'total_recurring_price': total_recurring_price,
  89. 'total_goods_value_share': 10,
  90. 'unit_recurring_price': total_recurring_price/2,
  91. 'unit_goods_value_share': 5,
  92. 'unit_staggered_goods_share': 5/36,
  93. 'unit_consolidated_cost': total_recurring_price/2 + 5/36,
  94. 'subscriptions_count': -1,
  95. })
  96. def test_similar(self):
  97. other_doc = Document.objects.create(name='other budget')
  98. a = Service.objects.create(
  99. name='Foo', document=self.doc, subscriptions_count=3)
  100. b = Service.objects.create(
  101. name='Foo', document=other_doc, subscriptions_count=3)
  102. c = Service.objects.create(
  103. name='Bar', document=other_doc, subscriptions_count=3)
  104. similars = Service.objects.similar_to(a)
  105. self.assertEqual(similars.count(), 1)
  106. self.assertEqual(similars.first(), b)
  107. class AbstractUseTests(TestCase):
  108. """ Testing AbstractUseTests through CostUse
  109. """
  110. def setUp(self):
  111. self.doc = Document.objects.create(name='budget')
  112. self.hosting_service = Service.objects.create(
  113. name='Physical hosting', document=self.doc)
  114. self.mailbox_service = Service.objects.create(
  115. name='Mailbox', document=self.doc)
  116. self.electricity_cost = Cost.objects.create(
  117. name='electricity',
  118. price=10,
  119. document=self.doc,
  120. capacity_unit='A',
  121. total_capacity=4,
  122. )
  123. self.carrier_connection = Cost.objects.create(
  124. name='carrier connection',
  125. price=100,
  126. document=self.doc,
  127. )
  128. def test_can_add_service_share(self):
  129. use = CostUse(
  130. service=self.hosting_service,
  131. resource=self.electricity_cost,
  132. share=0.4)
  133. use.full_clean()
  134. use.save()
  135. def test_can_add_service_share_with_custom_unity(self):
  136. use = CostUse(
  137. service=self.hosting_service,
  138. resource=self.electricity_cost,
  139. share=2) # means 2 Amps
  140. self.hosting_service.subscriptions_count = 2
  141. self.hosting_service.save()
  142. use.full_clean()
  143. use.save()
  144. self.assertEqual(use.share, 2.0)
  145. self.assertEqual(use.real_share(), 4.0)
  146. self.assertEqual(use.cost_share(), 10)
  147. self.assertEqual(use.unit_cost_share(), 5)
  148. def test_cannot_add_excess_share_one(self):
  149. use = CostUse(
  150. service=self.hosting_service,
  151. resource=self.electricity_cost,
  152. share=40.1)
  153. with self.assertRaises(ValidationError):
  154. use.full_clean()
  155. use.save()
  156. def test_add_several_service_share(self):
  157. u1 = CostUse(
  158. service=self.hosting_service,
  159. resource=self.electricity_cost,
  160. share=0.4)
  161. u1.full_clean()
  162. u1.save()
  163. u2 = CostUse(
  164. service=self.mailbox_service,
  165. resource=self.electricity_cost,
  166. share=0.6)
  167. u2.full_clean()
  168. u2.save()
  169. def test_add_several_service_share_excessive_sum(self):
  170. u1 = CostUse(
  171. service=self.hosting_service,
  172. resource=self.electricity_cost,
  173. share=3)
  174. u1.full_clean()
  175. u1.save()
  176. u2 = CostUse(
  177. service=self.mailbox_service,
  178. resource=self.electricity_cost,
  179. share=1.1)
  180. # Would be 4.1 out of 4 amp...
  181. with self.assertRaises(ValidationError):
  182. u2.full_clean()
  183. u2.save()
  184. def test_modify_service_share_no_error(self):
  185. u1 = CostUse(
  186. service=self.hosting_service,
  187. resource=self.electricity_cost,
  188. share=1)
  189. u1.full_clean()
  190. u1.save()
  191. u1.full_clean()
  192. u1.save()
  193. def test_real_shares(self):
  194. u1 = CostUse.objects.create(
  195. service=self.hosting_service,
  196. resource=self.electricity_cost,
  197. share=1.6)
  198. u2 = CostUse.objects.create(
  199. service=self.hosting_service,
  200. resource=self.electricity_cost,
  201. share=0.6)
  202. self.assertEqual(u1.real_share() + u2.real_share(), 4)
  203. self.assertEqual(u1.share/u2.share, u1.real_share()/u2.real_share())
  204. def test_unit_value_share(self):
  205. self.mailbox_service.subscriptions_count = 2
  206. self.mailbox_service.share = 0.5
  207. self.hosting_service.save()
  208. self.hosting_service.subscriptions_count = 1
  209. self.hosting_service.share = 0.5
  210. self.hosting_service.save()
  211. mailbox_use = CostUse.objects.create(
  212. service=self.mailbox_service,
  213. resource=self.electricity_cost,
  214. share=2)
  215. hosting_use = CostUse.objects.create(
  216. service=self.hosting_service,
  217. resource=self.electricity_cost,
  218. share=2)
  219. self.assertEqual(mailbox_use.value_share(), 5)
  220. self.assertEqual(mailbox_use.unit_value_share(), 2.5)
  221. self.assertEqual(hosting_use.value_share(), 5)
  222. self.assertEqual(hosting_use.unit_value_share(), 5)
  223. def test_used(self):
  224. CostUse.objects.create(
  225. service=self.mailbox_service,
  226. resource=self.electricity_cost,
  227. share=0.5)
  228. self.assertEqual(self.electricity_cost.used(), 0.5)
  229. self.assertEqual(self.electricity_cost.unused(), 3.5)
  230. def test_service_using_service(self):
  231. """
  232. Wifi+VPN is a service, but using VPN access
  233. So there is a service using another service
  234. """
  235. vpn_service = Service.objects.create(
  236. name="VPN",
  237. document=self.doc,
  238. subscriptions_count=20, # includes wifi+vpn subscribers
  239. reusable=True,
  240. )
  241. # both should be auto-set
  242. self.assertEqual(vpn_service.capacity_unit, 'services')
  243. self.assertEqual(vpn_service.total_capacity, 20)
  244. wifi_service = Service.objects.create(
  245. name="Wifi, via VPN",
  246. document=self.doc,
  247. subscriptions_count=2,
  248. reusable=True,
  249. )
  250. self.assertEqual(vpn_service.capacity_unit, 'services')
  251. # To simplify, VPN is only using electricity
  252. CostUse.objects.create(
  253. service=vpn_service,
  254. resource=self.electricity_cost,
  255. share=0.5, # Amp
  256. )
  257. # Wifi is using VPN + a carrier connection
  258. wifi_vpn_use = ServiceUse.objects.create(
  259. service=wifi_service,
  260. resource=vpn_service,
  261. share=2,
  262. )
  263. CostUse.objects.create(
  264. service=wifi_service,
  265. resource=self.carrier_connection,
  266. share=1, # 100%
  267. )
  268. self.assertEqual(wifi_vpn_use.share, 0.5*4*10/20*2)
  269. self.assertEqual(wifi_vpn_use.unit_share(), 0.5*4*10/20)
  270. # VPN this is the only service using electricity
  271. self.assertEqual(wifi_vpn_use.unit_real_share(), 10)
  272. # VPN is now using some gear, with deprecation provisioning
  273. hosting_access_fee = Good.objects.create(
  274. name='hosting access fee', price=360,
  275. provisioning_duration=THREE_YEARS, document=self.doc)
  276. GoodUse.objects.create(
  277. service=vpn_service, resource=hosting_access_fee, share=2)
  278. self.assertEqual(
  279. wifi_service.get_prices()['total_goods_value_share'], 36)
  280. self.assertEqual(
  281. wifi_service.get_prices()['unit_goods_value_share'], 18)
  282. def test_service_using_non_usable_service(self):
  283. serva = Service.objects.create(
  284. name='A', document=self.doc,
  285. subscriptions_count=4,
  286. reusable=False,
  287. )
  288. with self.assertRaises(ValidationError):
  289. su = ServiceUse(
  290. service=self.mailbox_service,
  291. resource=serva,
  292. share=1,
  293. )
  294. su.full_clean()
  295. su.save()
  296. def test_service_using_cyclic_service(self):
  297. """ We should not save any service dependency building a cycle
  298. """
  299. a = Service.objects.create(
  300. name='a', document=self.doc,
  301. reusable=True,
  302. )
  303. b = Service.objects.create(
  304. name='b', document=self.doc,
  305. reusable=True,
  306. )
  307. c = Service.objects.create(
  308. name='c', document=self.doc,
  309. reusable=True,
  310. )
  311. def create_clean(user, used):
  312. new_use = ServiceUse(service=user, resource=used, share=1)
  313. new_use.full_clean()
  314. new_use.save()
  315. create_clean(a, b)
  316. with self.assertRaises(ValidationError):
  317. create_clean(b, a)
  318. create_clean(b, c)
  319. with self.assertRaises(ValidationError):
  320. create_clean(c, a)
  321. class TestDocuments(TestCase):
  322. fixtures = ['full_example.yaml']
  323. _all_models = [Document, Cost, Good, Service, GoodUse, CostUse, ServiceUse]
  324. def _count(self):
  325. return [i.objects.count() for i in self._all_models]
  326. def test_copy(self):
  327. initial_counts = self._count()
  328. old_doc = Document.objects.first()
  329. new_doc = old_doc.copy()
  330. self.assertNotEqual(new_doc.pk, old_doc.pk)
  331. self.assertEqual(self._count(), [i*2 for i in initial_counts])
  332. old_doc.delete()
  333. self.assertEqual(self._count(), initial_counts)
  334. def test_compare(self):
  335. doc1 = Document.objects.create(name='a budget')
  336. doc2 = Document.objects.create(name='a budget')
  337. Service.objects.create(
  338. name='Foo', document=doc2, subscriptions_count=3)
  339. Service.objects.create(
  340. name='Bar', document=doc2, subscriptions_count=3)
  341. Service.objects.create(
  342. name='Bar', document=doc1, subscriptions_count=3)
  343. comparison = doc2.compare(doc1)
  344. self.assertEqual(len(comparison['services']), 2)
  345. self.assertIsNone(comparison['services'][0]['other_service'])
  346. self.assertIsNotNone(comparison['services'][1]['other_service'])