common.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. # -*- coding: utf-8 -*-
  2. # !/usr/bin/env python
  3. import sys
  4. from django.conf import settings
  5. from typing import Union, Callable
  6. import contextlib
  7. import functools
  8. from django.core.urlresolvers import reverse
  9. from apilib.monetary import RMB, Permillage
  10. from apilib.utils_string import md5
  11. from apilib.utils_json import json_loads
  12. from apps.web.constant import AdType, AdScriptType, Const
  13. from testcase.library.concurrency import run_concurrently
  14. ### Data Fixtures
  15. DEALER_ID = '5b87a2497cc02823fcf01108'
  16. GROUP_ID = '5b87a2487cc02823fcf01106'
  17. PARTNER_GROUP_ID = '5bb28bcb7cc02828e407c4bd'
  18. PARTNER_ID = '5bb28bf57cc02828e407c4be'
  19. DEVICE_ID = '5b87a2497cc02823fcf01107'
  20. AGENT_ID = '5b87a2497cc02823fcf01109'
  21. DEFAULT_AGENT_ID ='5b87a2497cc02323fcf01102'
  22. MY_USER_ID = '5b87a2497cc02823fcf0110a'
  23. MANAGER_ID = '5b87a2497cc02823fcf0110b'
  24. DEFAULT_MANAGER_ID = '5b88a2497cc02823fcf0112a'
  25. SUPER_MANAGER_ID = '5b87a2497cc02823fcf0110c'
  26. DEVICE_TYPE_ID = '5c482f37cdc57a252cb90566'
  27. # Dealer Income
  28. RECHARGE_RECORD_ID = '5b8fc2177cc0282f04be33db'
  29. AD_RECORD_ID = '5b8fc20d7cc0282f04be33da'
  30. CARD_RECHARGE_RECORD_ID = '5b8fc1f97cc0282f04be33d8'
  31. ON_SALE_ID = '5c372eb7cdc57a32bca62aba'
  32. # Dealer Consumption
  33. CONSUME_RECORD_ID = '5b8fc2027cc0282f04be33d9'
  34. # Ad
  35. PAY_AFTER_AD_ID = '5c2d71f5cdc57a223cf8aa15'
  36. BANNER_AD_ID = '5cadacaacdc57a1d6c3e55de'
  37. # BankCard
  38. MERCHANT_ID = '59a6ab388732d6169b33cde3'
  39. BANK_ACCOUNT_CODE = '6217561400008706928'
  40. class DEFAULT_WECHAT_CONFIG:
  41. appid = 'default_auth_app_id'
  42. mchid = 'default_mch_id'
  43. secret = 'default_secret'
  44. apikey = 'default_apikey'
  45. sslCert = '-----BEGIN CERTIFICATE-----MIIEaTCCA9KgAwIBAgIEAa1TIDANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCb25nMREwDwYDVQQHEwhTaGVuemhlbjEQMA4GA1UEChMHVGVuY2VudDEOMAwGA1UECxMFTU1QYXkxLTArBgNVBAMUJOatpuaxieW+ruW8l+aZuumAmuenkeaKgOaciemZkOWFrOWPuDERMA8GA1UEBBMIOTU2MDQ4MDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWH8s3cwbybiAISJBUI6o0iyaRj7VVtFHr3JC5fb0iMxwV4KdnP9qOmtqqiMK+viSk6Hna42DZl2JxtC6Ei2lNdb/B2OxdwlMkk71PbDRG7lvHvdmtc2tuPc6QCOc+D+yNpj860dN8t3eS44WsVrCRwF/a03jDKS2uRVolKqxK+3Jk3J+e8QckPf18dkt/08C6HQQsGBoE6rA4Trbzz2bpXOmLQ5yzfa9d4HiVynN9m6p2yb62mIQkeC+0KvxzinXz9VBhuhcOhCS6sA7Xlub5IppNfwkgUpQZnopTgsqDZe4Ji/SO7NCOxjZW1mAM1NAGoMgO2rrOukn2JwSa8XHBAgMBAAGjggFGMIIBQjAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh0iQ0VTLUNBIEdlbmVyYXRlIENlcnRpZmljYXRlIjAdBgNVHQ4EFgQUYJr4uGXkipVedmdhMcLItKMxibkwgb8GA1UdIwSBtzCBtIAUPgUm9iJitBVbiM1kfrDUYqflhnShgZCkgY0wgYoxCzAJBgNVBAYTAkNOMRIwEAYDVQQIEwlHdWFuZ2RvbmcxETAPBgNVBAcTCFNoZW56aGVuMRAwDgYDVQQKEwdUZW5jZW50MQwwCgYDVQQLEwNXWEcxEzARBgNVBAMTCk1tcGF5bWNoQ0ExHzAdBgkqhkiG9w0BCQEWEG1tcGF5bWNoQHRlbmNlbnSCCQC7VJcrvADoVzAOBgNVHQ8BAf8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEASLF+dH6iI6UK62HerRrwN02w/4uoiJle4ij3sEDuh82B3lWyEsy06RZEhxhlX7fSmg7UUUPjUmSVOwT7Ss9eOhpBZjeBgBsLBIeYk1XZq6IVN78bx0soiaQ4TTfWqYdAaQ0XaWB+540ZW3CkKU87XLP0uFDSPulSd68iZ05qTCc=-----END CERTIFICATE-----'
  46. sslKey = '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWH8s3cwbybiAI\nSJBUI6o0iyaRj7VVtFHr3JC5fb0iMxwV4KdnP9qOmtqqiMK+viSk6Hna42DZl2Jx\ntC6Ei2lNdb/B2OxdwlMkk71PbDRG7lvHvdmtc2tuPc6QCOc+D+yNpj860dN8t3eS\n44WsVrCRwF/a03jDKS2uRVolKqxK+3Jk3J+e8QckPf18dkt/08C6HQQsGBoE6rA4\nTrbzz2bpXOmLQ5yzfa9d4HiVynN9m6p2yb62mIQkeC+0KvxzinXz9VBhuhcOhCS6\nsA7Xlub5IppNfwkgUpQZnopTgsqDZe4Ji/SO7NCOxjZW1mAM1NAGoMgO2rrOukn2\nJwSa8XHBAgMBAAECggEAOijuoMQMqgLBjp5vHXuGerklI7p+DaZp7f++C7QckFKD\nmWmo7sGIUt268UEubHLnLye6GXtTTibxZWMEk5nuY0zNZhDOuvTScofyjsUcOfv6\nT/nAS32jO5FsLvFMJi5bGWqDmC1wHVsUDoDgSRxO33f6UUzJa3Ee6EwMHTZHECJW\nuaMQOzddnivs2S17Nk366ckJrPeP5BR4UT+Ulfi4D8kDxx6SLgrqdSHb1ppgpeiW\ncLGRrCIm/DxkAVKi1ATNVrxhnpeBpLa46Q0VUytXvAh2/IQVwGn53T95Uf1QX4B8\nnc3ztHMJ90VfFe0Yg67CPOFfbnpmfwB0tKbVjumvwQKBgQD8HNA+nqcKFyow6YMy\na6XIv/7HM4CWvz37GwZGfdji+kx8nl7eBnX08BeS4bzvsHp4z2SxdHLDQdYbSeQb\naxSCsM8DUbcl7VMDdrsSEsRhsuXaZSD1sGUa/bDLmVNbYZv8lM25mHp/LDgV5Hd/\ndYQYYjLHxws7qlKHW/qwlUCCWQKBgQDZbQaHFBy0VPnBI7THT3EY12q9VnRhqx1S\nMKfrJ0O6RfCiiV7Z9m43SS4VfEeCu6OC8mSDsSTq6n6Goobm64CWUrp+jHb3VtQe\nHoXUXk2uxezcmTZWN2YkUHGrjOsh7AFPJTxNmE+RIegiNhxX7fj6jITy84ZPnvLF\n7+f8VEftqQKBgGWpTMPjf+IOw0Gj9/aN9/UCjr9O4kxnY5vtItOsDZhBbDRYPFL1\ncf1KD4d4JZLVrB0pLCZumZRo6OhqHGR+AnBM0b0550uQ7oeYCglYrf3jgNJXMzo/\nXhXLNM50zmsr6hWo/2gWt1auwxL45MLt5U1b07RBbCMsJElW6rjoBnJJAoGAMYTB\nLdaIyxFnaHOHYrVXHr9BEio0i2H7ZZKrS8U/iZcQPRRoQ9/6LiE+m6oYZ0/w54vp\nFI0tyUrq0iMF8mXEfXgN/XBkT+S4nrt442su6hUt4vgKBgzL+I1Rti58BtfHXI0w\ndMOxq247fj8wqV4LjJSgWuzXtB6RUD9ZaICReTECgYEAykLb5bWQ3VpM/mRNYSfq\noBxbmt4Cjo3P7wD56/Dhxhhwh1bxU/2XuyZQD3IJeUzK7Mk16tUat0O3h8iayiV/\nw9S6iB0DtpQZVl2N3TgiKT8tDHx+MSwZdFKv3hLWK1EKaZ3rmOaq3zUpV7dufijT\nfYl5/FUXNSrW654qwl5MX1o=\n-----END PRIVATE KEY-----\n'
  47. manual_withdraw = False
  48. class DEFAULT_ALIPAY_CONFIG:
  49. appid = 'default_alipay_app_id'
  50. public_key_path = './'
  51. app_private_key_path = './'
  52. class WECHAT_CONFIG:
  53. appid = 'test_auth_app_id'
  54. mchid = 'test_mch_id'
  55. secret = 'test_secret'
  56. apikey = 'test_apikey'
  57. sslCert = '-----BEGIN'
  58. sslKey = '-----BEGIN'
  59. manual_withdraw = False
  60. class ALIPAY_CONFIG:
  61. appid = 'test_alipay_app_id'
  62. alipayPublicKey = '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1YUBQoIfVbejOGPPEuQOPb60LBLsNzgrfi92yRUwnDJJO7GeqpEhCLbn8THuX+BQsZmZAHIigVnXqGXNBR+IFv9dl2W/JA9d3Y83+zwNidlPLJSbtFONqBBeTo01en109vesDLLTD0fHjuZzsoPbtXLJFu0YA6uvO5q4rGgjK4bc9fnqJti6tuoS/r/YyV1VnLwL9Gv+LKIUY7cRERkwPBTTsUT4OZ0K/cGhclnnE8VpaHfeov4GK7vaR5VHIerUN5uclg/Nzp8l0GMijF3hg9DfKhvD1Ss7YB8l9PxS9C8inLqBeAO5JQqQmdJH2VwuSsFoZVH7Zh4j1jp42JvuzQIDAQAB\n-----END PUBLIC KEY-----'
  63. appPrivateKey = '-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4VOby1di+q1O5RaEdPndj9BN0OU4GOTTIvlpxY4rxYnJa7Xe\nHtQq+dMPYYw7W5cIeHFw+EoD5Pf+2uSOedNHW4wtfEH5N2EsyaaK2s7izJZxlGXn\nxrJTV0WlQ6rWKXg0FNt1swbtS24IA/LNT+9LjpSUvkWSuQaABZcm3GzuG2BvRgx7\nWh3cnT3JMq4xOkKk9fAsam5Hsxrd2bpzEjreRLa7xynFkXjZhNlzX+CNZnRjP4L1\n5Vq3C4ZeE7YfHSRFTCNPs11Fx3HPMFEe49Xa6r6iayp934C4fax3WkL1vukeQDSr\nZbVMDB1mwMhpmPlKqsp0S53fp/dkOUbScVyqwQIDAQABAoIBACbXQ7bEhIJTl5Lz\n07daTu/4Z0GIEahw4VhZB/uO60qs2rSfb/9+cT4JPNCC3MSH/2TLoT6be5pfiWXw\nGgM0AUvtmPWfZbc/H/pOpAbdvMipckCzZEWi/azAi6FjdHFHkCq8Pcize/oRqH4N\nv4h05mBYil9WHNmaHj1YddFvUYgUP6hYxTcheb4ilyQfw+xwGJryGP/qESZvT11M\n3fIJc6C2brDqch718JD3Cfn/NhdkrAjxxKVP4XkNd8CXqNLYU77gl0JTG2I1Yd06\nd+RtdHYMI6ZrhSTH6TUq4OowAE1rtHOIA1hvK1UPCTxtS9U2g37HNzZXtTaN3hAg\nhFgMlcECgYEA60Bi4lKqjwB3zLypxeby6pCmEhLXbmQhnmdF/hkL+VTwHdXU1i15\nNoQFRJXIKkYXrUWnjsLg+IETM0WWiP6FwxFcnsFnI9DKYeDBKgj5WaKne2Xb82JW\nZfdyi6k+B0ZoTPq6GnoId/HkWpGbTBuv3Ej3q/p8Pb7oQAvw5TTwNGkCgYEA9TMi\nGHnlFhogNy6MBVjLgDlm7cSWKuYiSo9MBjtYDdX2t4TZ3O95le4Y5FMweUZqRmcK\nqPxB491j2lXDGRC/Tuu0ZpjCilrjbm4UOa2q7O6clijhaoHyDj7MFN1kJDZ4Hlu+\ncDAq1A1MPBQynaukpdlvHsFq+SBN90k30odXmJkCgYEA2FaBmPfEeXVItjLZPXFA\n+ozC6+P/0Tir4eu+5glKhaOinQ1DiSKQUZ3e6k+Dn7yNkX54tERqdpzcd04LPTgy\n8kxJDIV2v+0F8DSr7mgKRm/Z7RSizJMmOObxo5WjwTTyPbhk5GxM1WDRSqiUU80y\nUDWZ7cGP94hUNHHGukGQk+ECgYEAl4e7GW8zMN6CbDe61o8SbDKNNoFX4DkKlhNK\ng0tnVl2VM8LJNK1V8V8MSQZUHY5znQCO6PHD8oLd848um63q3GXzvbfH+eLDTHqA\ny0hVKkknwfCPpdeadE1Ap+BNw8dZokFGRmSuAxAYvcQpp6DshoYCEvqrFjxtklXQ\nRq3XZ3kCgYEA0GI6xg1zfyT/U5gjHq9IOOXfZJaKpKocye2DPr0X5O8AXVz56+5j\nnNWLfRm1/0tgsPjo/V1YemzTmGt1KAue7hHdzguE8Fx6hsrBLf6dMQKJvADqm0oB\n/Ui65+R5SS2hZNyJ3BxYn0pK83+6I/IWs7OkSTCEz0mDaOdgaBXRlf8=\n-----END RSA PRIVATE KEY-----'
  64. DEALER_FIXTURE = {
  65. 'id': DEALER_ID,
  66. 'username': '1880188088',
  67. 'raw_password': '1234567',
  68. 'password': md5('1234567'),
  69. 'nickname': u'测试经销商',
  70. 'agentId': AGENT_ID,
  71. 'withdrawFeeRatio': Const.PLATFORM_DEFAULT_WITHDRAW_FEE_RATIO + Permillage(2),
  72. 'defaultDiscountConfig': {}
  73. }
  74. PARTNER_FIXTURE = {
  75. 'id': PARTNER_ID,
  76. 'username': '1779292312',
  77. 'password': md5('1234567'),
  78. 'nickname': u'测试合伙人',
  79. 'agentId': AGENT_ID
  80. }
  81. SOLE_GROUP_FIXTURE = {
  82. 'id' : GROUP_ID,
  83. 'ownerId': DEALER_ID,
  84. 'groupName': u'测试单一经销商设备组',
  85. 'address': u'测试地址单一',
  86. 'districtId': u'2000272',
  87. 'addressType': u'school',
  88. }
  89. PARTNER_GROUP_FIXTURE = {
  90. 'id': PARTNER_GROUP_ID,
  91. 'ownerId': DEALER_ID,
  92. 'groupName': u'测试有合伙人的组',
  93. 'address': u'测试地址合伙',
  94. 'districtId': u'2000272',
  95. 'addressType': u'workshop',
  96. 'partnerDict': ['']
  97. }
  98. DEFAULT_DEVICE_TYPE_FIXTURE = {
  99. 'name': 'test_normal_device_type',
  100. 'code': 'test_device_type_code',
  101. 'online': True
  102. }
  103. DEVICE_FIXTURE = {
  104. 'id': DEVICE_ID,
  105. 'logicalCode': 'logicalCode_via_system_test',
  106. 'devNo': '888888888888888',
  107. 'devType': DEFAULT_DEVICE_TYPE_FIXTURE,
  108. 'groupId': GROUP_ID,
  109. 'ownerId': DEALER_ID,
  110. 'groupNumber': '1',
  111. 'washConfig': {
  112. "1" : {
  113. "description" : "",
  114. "price" : 0.01,
  115. "coins" : 1,
  116. "time" : 1,
  117. "imgList" : [],
  118. "unit" : "分钟",
  119. "name" : "套餐1"
  120. },
  121. "3" : {
  122. "description" : "",
  123. "price" : 3.0,
  124. "coins" : 3,
  125. "time" : 15,
  126. "imgList" : [],
  127. "unit" : "分钟",
  128. "name" : "套餐3"
  129. },
  130. "2" : {
  131. "description" : "",
  132. "price" : 2.0,
  133. "coins" : 2,
  134. "time" : 5,
  135. "imgList" : [],
  136. "unit" : "分钟",
  137. "name" : "套餐2"
  138. },
  139. "4" : {
  140. "description" : "",
  141. "price" : 4.0,
  142. "coins" : 4,
  143. "time" : 30,
  144. "imgList" : [],
  145. "unit" : "分钟",
  146. "name" : "套餐4"
  147. }
  148. }
  149. }
  150. DEVICE_TYPE_FIXTURE = {
  151. 'id': DEVICE_TYPE_ID,
  152. "name": "洗衣机",
  153. "code": "100028",
  154. "online": True,
  155. "package": [
  156. {
  157. "price": 1,
  158. "coins": 1,
  159. "name": "单脱水",
  160. "time": 6
  161. }
  162. ],
  163. "timeBased": True,
  164. "managerId": MANAGER_ID,
  165. "agentId": AGENT_ID,
  166. }
  167. AGENT_FIXTURE = {
  168. 'id': AGENT_ID,
  169. 'username': '1770177077',
  170. 'password': md5('1234567'),
  171. 'nickname': u'测试代理商',
  172. 'agentSign': 'test_sign',
  173. 'managerId': MANAGER_ID,
  174. 'customizedAlipayCashflowAllowable': True,
  175. 'customizedWechatCashflowAllowable': True
  176. }
  177. AGENT_NO_CUSTOMIZED_FIXTURE = {
  178. 'id': AGENT_ID,
  179. 'username': '1770177077',
  180. 'password': settings.UNIVERSAL_PASSWORD,
  181. 'nickname': u'无自定义测试代理商',
  182. 'agentSign': 'test_sign',
  183. 'managerId': DEFAULT_MANAGER_ID,
  184. 'customizedAlipayCashflowAllowable': False,
  185. 'customizedWechatCashflowAllowable': False,
  186. 'customizedUserGzhAllowable': False,
  187. 'customizedDealerGzhAllowable': False,
  188. 'customizedWechatMiniAllowable': False
  189. }
  190. DEFAULT_AGENT_FIXTURE = {
  191. 'id': DEFAULT_AGENT_ID,
  192. 'username': '1778822222',
  193. 'password': settings.UNIVERSAL_PASSWORD,
  194. 'nickname': u'测试默认代理商',
  195. 'agentSign': 'test_sign',
  196. 'managerId': DEFAULT_MANAGER_ID,
  197. 'customizedAlipayCashflowAllowable': True,
  198. 'customizedWechatCashflowAllowable': True,
  199. 'customizedUserGzhAllowable': True,
  200. }
  201. MY_USER_FIXTURE = {
  202. 'id': MY_USER_ID,
  203. 'openId': 'test_openId',
  204. 'promo': {'inhouse_openId': 'test_inhouse_openId'},
  205. 'groupId': GROUP_ID,
  206. 'agentId': AGENT_ID,
  207. 'nickname': 'test enduser',
  208. 'authAppId': WECHAT_CONFIG.appid,
  209. 'payAppId': WECHAT_CONFIG.appid,
  210. 'balance': RMB(0)
  211. }
  212. MANAGER_FIXTURE = {
  213. 'id': MANAGER_ID,
  214. 'nickname': 'test manager',
  215. 'username': '1660166066',
  216. 'password': md5('1234567'),
  217. 'primeAgentId': AGENT_ID
  218. }
  219. DEFAULT_MANAGER_FIXTURE = {
  220. 'id': DEFAULT_MANAGER_ID,
  221. 'nickname': 'test default manager',
  222. 'username': '1898989999',
  223. 'password': md5('1234567'),
  224. 'primeAgentId': DEFAULT_AGENT_ID
  225. }
  226. SUPER_MANAGER_FIXTURE = {
  227. 'id': SUPER_MANAGER_ID,
  228. 'username': '1660166222',
  229. 'password': md5('1234567')
  230. }
  231. PAY_AFTER_AD_FIXTURE = {
  232. 'id': PAY_AFTER_AD_ID,
  233. 'name': 'test pay after ad',
  234. 'managerId': MANAGER_ID,
  235. 'adType': AdType.PAY_AFTER,
  236. 'scriptType': AdScriptType.INLINE,
  237. 'configs': {'autoRedirect': True},
  238. 'script': "alert('test it')"
  239. }
  240. BANNER_AD_FIXTURE = {
  241. 'id': BANNER_AD_ID,
  242. 'name': 'test banner ad',
  243. 'link': 'http://www.baidu.com',
  244. 'managerId': MANAGER_ID
  245. }
  246. ON_SALE_FIXTURE = {
  247. 'id': '5c372eb7cdc57a32bca62aba',
  248. "dealerId" : DEALER_ID,
  249. "name" : u"首次使用送金币",
  250. "showSite" : 0,
  251. "logicalCodeList" : [
  252. DEVICE_FIXTURE['logicalCode']
  253. ],
  254. "onsaleType" : u"首次使用送金币",
  255. "detailDict" : {
  256. "coins" : "10",
  257. "duration": "1"
  258. },
  259. "desc" : "适用所有设备类型,用户首次扫码的时候,允许客户领金币进行体验,本次活动领过金币的用户,下次无法领取金币。",
  260. "img" : "/app/img/marketing/first_give_coins.jpg",
  261. "onClickUrl" : "/user/sendCoins",
  262. "status" : "start",
  263. "showType" : "onlyOne"
  264. }
  265. ON_SALE_RECORD = {
  266. 'id': '5c384d44cdc57a25408ed300'
  267. }
  268. MERCHANT_FIXTURE = {
  269. "id": MERCHANT_ID,
  270. "parentBankName": "中国银行",
  271. "merchantName": "张某某",
  272. "accountCode": BANK_ACCOUNT_CODE,
  273. "cardType": "借记卡",
  274. "ownerId": DEALER_ID,
  275. 'accountType': 'private'
  276. }
  277. BANK_CARD_FIXTURE = {
  278. "id": MERCHANT_ID,
  279. "bankName": "中国银行",
  280. "branchName": u"光谷支行",
  281. "holderName": "张某某",
  282. "code":"1026",
  283. "cardNo": BANK_ACCOUNT_CODE,
  284. "cardType": "借记卡"
  285. }
  286. class DisposableModel(object):
  287. def __init__(self, model, **kwargs):
  288. self.model = model
  289. self.kwargs = kwargs
  290. def __enter__(self):
  291. instance = self.model(**self.kwargs).save()
  292. self._instance = instance
  293. return instance
  294. def __exit__(self, exc_type, exc_val, exc_tb):
  295. self._instance.delete()
  296. def url_fn(view_fn):
  297. def url_fn_aux(**kw):
  298. query_sign = '?' if kw else ''
  299. from six.moves.urllib import parse
  300. return reverse(view_fn) + query_sign + parse.urlencode(kw)
  301. return url_fn_aux
  302. def json_is_the_same(a, b):
  303. # type: (Union[str, dict], Union[str, dict])->bool
  304. if isinstance(a, basestring) and isinstance(b, basestring):
  305. return json_loads(a) == json_loads(b)
  306. elif isinstance(a, dict) and isinstance(b, dict):
  307. return a == b
  308. else:
  309. raise TypeError('a and b has to be both str or dict')
  310. def is_concurrently_safe(fn):
  311. #type: (Callable)->bool
  312. num_threads = 10
  313. responses = run_concurrently(
  314. fn,
  315. num_threads=num_threads)
  316. succeeded_response = [ _ for _ in responses if _['result'] == 1]
  317. return len(succeeded_response) == 1 and len([ _ for _ in responses if _ not in succeeded_response]) == num_threads - 1
  318. @contextlib.contextmanager
  319. def mocked(func,
  320. expecting=None, returning=None, raising=None, # specs
  321. replacement_logic=None, called=1):
  322. """Stub out and mock a function for a yield's duration.
  323. borrowed from http://blog.moertel.com/posts/2011-11-07-a-flyweight-mocking-helper-for-python.html
  324. >>> def some_func(): return 1
  325. >>> with mocked(some_func, returning=2):
  326. >>> ...
  327. """
  328. if (returning, raising, replacement_logic).count(None) < 2:
  329. raise ValueError("returning, raising, and replacement_logic "
  330. "are incompatible with each other")
  331. # default logic for implementing mock fn: return or raise per specs
  332. def default_logic(*_args, **_kwargs):
  333. if raising:
  334. raise raising
  335. return returning
  336. # prepare wrapper to replace mocked function for duration of yield
  337. invocations = [0]
  338. @functools.wraps(func)
  339. def replacement(*args, **kwargs):
  340. if expecting is not None:
  341. assert expecting == (args, kwargs) # did we get expected args?
  342. invocations[0] += 1
  343. return (replacement_logic or default_logic)(*args, **kwargs)
  344. # replace mocked function, yield to test, and then check & clean up
  345. module = sys.modules.get(func.__module__)
  346. setattr(module, func.__name__, replacement)
  347. try:
  348. yield # give control back to test for a while
  349. assert invocations[0] == called # was mock called enough?
  350. finally:
  351. setattr(module, func.__name__, func)
  352. def uncalled(func):
  353. """Require that a function not be called for a yield's duration."""
  354. return mocked(func, called=0)