stoobly-agent 0.21.0__py3-none-any.whl → 0.22.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. stoobly_agent/__init__.py +2 -1
  2. stoobly_agent/app/api/bodies_controller.py +2 -2
  3. stoobly_agent/app/api/requests_controller.py +51 -29
  4. stoobly_agent/app/api/simple_http_request_handler.py +1 -1
  5. stoobly_agent/app/cli/ca_cert_installer.py +16 -5
  6. stoobly_agent/app/cli/config_cli.py +225 -135
  7. stoobly_agent/app/cli/dev_tools_cli.py +3 -1
  8. stoobly_agent/app/cli/helpers/__init__.py +2 -1
  9. stoobly_agent/app/cli/helpers/context.py +7 -0
  10. stoobly_agent/app/cli/helpers/handle_mock_service.py +14 -0
  11. stoobly_agent/app/cli/helpers/print_service.py +8 -0
  12. stoobly_agent/app/cli/helpers/scenario_facade.py +10 -28
  13. stoobly_agent/app/cli/intercept.py +46 -2
  14. stoobly_agent/app/cli/main_group.py +8 -9
  15. stoobly_agent/app/cli/project_cli.py +10 -27
  16. stoobly_agent/app/cli/request_cli.py +14 -18
  17. stoobly_agent/app/cli/scenario_cli.py +94 -87
  18. stoobly_agent/app/models/adapters/mitmproxy/__init__.py +2 -0
  19. stoobly_agent/app/models/adapters/mitmproxy/request/__init__.py +12 -0
  20. stoobly_agent/app/models/adapters/mitmproxy/request/python_adapter.py +16 -0
  21. stoobly_agent/app/models/adapters/mitmproxy/response/__init__.py +12 -0
  22. stoobly_agent/app/models/adapters/mitmproxy/response/python_adapter.py +24 -0
  23. stoobly_agent/app/models/adapters/python/__init__.py +2 -0
  24. stoobly_agent/app/models/adapters/python/request/__init__.py +18 -0
  25. stoobly_agent/app/models/adapters/python/request/mitmproxy_adapter.py +53 -0
  26. stoobly_agent/app/models/adapters/python/request/stoobly_adapter.py +49 -0
  27. stoobly_agent/app/models/adapters/python/response/__init__.py +13 -0
  28. stoobly_agent/app/models/adapters/{mitmproxy_response_adapter.py → python/response/mitmproxy_adapter.py} +15 -7
  29. stoobly_agent/app/models/adapters/raw_http_request_adapter.py +19 -12
  30. stoobly_agent/app/models/adapters/raw_http_response_adapter.py +22 -12
  31. stoobly_agent/app/models/adapters/stoobly/request/__init__.py +11 -0
  32. stoobly_agent/app/models/adapters/stoobly/request/mitmproxy_adapter.py +32 -0
  33. stoobly_agent/app/models/body_model.py +2 -2
  34. stoobly_agent/app/models/factories/__init__.py +0 -0
  35. stoobly_agent/app/models/factories/resource/__init__.py +0 -0
  36. stoobly_agent/app/models/{adapters/body_adapter_factory.py → factories/resource/body.py} +1 -1
  37. stoobly_agent/app/models/{adapters/header_adapter_factory.py → factories/resource/header.py} +1 -1
  38. stoobly_agent/app/models/factories/resource/local_db/__init__.py +0 -0
  39. stoobly_agent/app/models/{adapters → factories/resource}/local_db/header_adapter.py +2 -2
  40. stoobly_agent/app/models/{adapters → factories/resource}/local_db/replayed_response_adapter.py +1 -2
  41. stoobly_agent/app/models/{adapters → factories/resource}/local_db/request_adapter.py +15 -8
  42. stoobly_agent/app/models/{adapters → factories/resource}/local_db/scenario_adapter.py +13 -7
  43. stoobly_agent/app/models/{adapters/query_param_adapter_factory.py → factories/resource/query_param.py} +1 -1
  44. stoobly_agent/app/models/{adapters/replayed_response_adapter_factory.py → factories/resource/replayed_response.py} +1 -1
  45. stoobly_agent/app/models/{adapters/request_adapter_factory.py → factories/resource/request.py} +2 -2
  46. stoobly_agent/app/models/{adapters/response_adapter_factory.py → factories/resource/response.py} +1 -1
  47. stoobly_agent/app/models/{adapters/response_header_adapter_factory.py → factories/resource/response_header.py} +1 -1
  48. stoobly_agent/app/models/{adapters/scenario_adapter_factory.py → factories/resource/scenario.py} +2 -2
  49. stoobly_agent/app/models/factories/resource/stoobly/__init__.py +0 -0
  50. stoobly_agent/app/models/{adapters/stoobly_request_adapter.py → factories/resource/stoobly/request_adapter.py} +1 -2
  51. stoobly_agent/app/models/{adapters/stoobly_scenario_adapter.py → factories/resource/stoobly/scenario_adapter.py} +1 -2
  52. stoobly_agent/app/models/header_model.py +2 -2
  53. stoobly_agent/app/models/query_param_model.py +2 -2
  54. stoobly_agent/app/models/replayed_response_model.py +2 -2
  55. stoobly_agent/app/models/request_model.py +4 -5
  56. stoobly_agent/app/models/response_header_model.py +2 -2
  57. stoobly_agent/app/models/response_model.py +2 -2
  58. stoobly_agent/app/models/scenario_model.py +4 -4
  59. stoobly_agent/app/models/schemas/request.py +8 -1
  60. stoobly_agent/app/models/types/__init__.py +6 -0
  61. stoobly_agent/app/proxy/__init__.py +0 -5
  62. stoobly_agent/app/proxy/handle_mock_service.py +0 -3
  63. stoobly_agent/app/proxy/handle_test_service.py +1 -1
  64. stoobly_agent/app/proxy/intercept_handler.py +0 -1
  65. stoobly_agent/app/proxy/mitmproxy/flow_mock.py +35 -0
  66. stoobly_agent/app/proxy/mitmproxy/request_facade.py +11 -2
  67. stoobly_agent/app/proxy/mock/hashed_request_decorator.py +2 -2
  68. stoobly_agent/app/proxy/replay/replay_request_service.py +58 -40
  69. stoobly_agent/app/proxy/simulate_intercept_service.py +30 -0
  70. stoobly_agent/app/proxy/test/__init__.py +1 -0
  71. stoobly_agent/app/proxy/test/context.py +2 -1
  72. stoobly_agent/app/proxy/test/context_abc.py +155 -0
  73. stoobly_agent/app/proxy/test/matchers/context.py +1 -1
  74. stoobly_agent/app/proxy/test/matchers/contract.py +1 -1
  75. stoobly_agent/app/proxy/test/matchers/custom.py +1 -1
  76. stoobly_agent/app/proxy/test/matchers/diff.py +1 -1
  77. stoobly_agent/app/proxy/test/matchers/fuzzy.py +1 -1
  78. stoobly_agent/app/proxy/test/test_service.py +1 -1
  79. stoobly_agent/app/proxy/upload/joined_request.py +0 -1
  80. stoobly_agent/app/proxy/upload/proxy_request.py +4 -1
  81. stoobly_agent/app/proxy/upload/request_string.py +1 -1
  82. stoobly_agent/app/settings/constants/mode.py +3 -0
  83. stoobly_agent/app/settings/data_settings.py +3 -1
  84. stoobly_agent/cli.py +89 -11
  85. stoobly_agent/config/constants/env_vars.py +2 -1
  86. stoobly_agent/config/data_dir.py +29 -8
  87. stoobly_agent/config/settings.yml.sample +1 -1
  88. stoobly_agent/db/migrations/2022_12_12_092437_align_requests.py +4 -4
  89. stoobly_agent/lib/api/api.py +4 -4
  90. stoobly_agent/lib/api/keys/organization_key.py +2 -0
  91. stoobly_agent/lib/api/keys/project_key.py +13 -0
  92. stoobly_agent/lib/api/keys/resource_key.py +5 -3
  93. stoobly_agent/lib/api/scenarios_resource.py +4 -12
  94. stoobly_agent/lib/api/stoobly_api.py +9 -5
  95. stoobly_agent/lib/orm/__init__.py +1 -1
  96. stoobly_agent/lib/orm/migrate_service.py +28 -13
  97. stoobly_agent/lib/orm/replayed_response.py +3 -3
  98. stoobly_agent/lib/orm/request.py +25 -5
  99. stoobly_agent/lib/orm/transformers/orm_to_stoobly_request_transformer.py +15 -24
  100. stoobly_agent/mock.py +94 -0
  101. stoobly_agent/public/{2-es2015.3d54569af612a07a2e06.js → 1-es2015.37917aa26708d8f35d36.js} +1 -1
  102. stoobly_agent/public/{2-es5.3d54569af612a07a2e06.js → 1-es5.37917aa26708d8f35d36.js} +1 -1
  103. stoobly_agent/public/10-es2015.e9556b0d0f0e92fb548b.js +1 -0
  104. stoobly_agent/public/10-es5.e9556b0d0f0e92fb548b.js +1 -0
  105. stoobly_agent/public/11-es2015.bc6212fccbe72a623f81.js +1 -0
  106. stoobly_agent/public/11-es5.bc6212fccbe72a623f81.js +1 -0
  107. stoobly_agent/public/{13-es2015.76b6c147b0c46f995cd7.js → 12-es2015.d0768894ddffd6efa5e5.js} +1 -1
  108. stoobly_agent/public/{13-es5.76b6c147b0c46f995cd7.js → 12-es5.d0768894ddffd6efa5e5.js} +1 -1
  109. stoobly_agent/public/13-es2015.8a044490a76fd298162d.js +1 -0
  110. stoobly_agent/public/13-es5.8a044490a76fd298162d.js +1 -0
  111. stoobly_agent/public/{15-es2015.60c3b41c385f5bdedb7b.js → 14-es2015.1cd1a021e51ca0e62e1c.js} +1 -1
  112. stoobly_agent/public/{15-es5.60c3b41c385f5bdedb7b.js → 14-es5.1cd1a021e51ca0e62e1c.js} +1 -1
  113. stoobly_agent/public/{16-es2015.5d395009a77978db4405.js → 15-es2015.587781d19864ff0eb4f5.js} +1 -1
  114. stoobly_agent/public/{16-es5.5d395009a77978db4405.js → 15-es5.587781d19864ff0eb4f5.js} +1 -1
  115. stoobly_agent/public/16-es2015.ec6a175b1f9578203cd8.js +1 -0
  116. stoobly_agent/public/16-es5.ec6a175b1f9578203cd8.js +1 -0
  117. stoobly_agent/public/17-es2015.ad9c4756c96a15bd29d7.js +1 -0
  118. stoobly_agent/public/17-es5.ad9c4756c96a15bd29d7.js +1 -0
  119. stoobly_agent/public/{19-es2015.517f68e08f4c582dae66.js → 18-es2015.8583df0f8eccb3e47c0b.js} +1 -1
  120. stoobly_agent/public/{19-es5.517f68e08f4c582dae66.js → 18-es5.8583df0f8eccb3e47c0b.js} +1 -1
  121. stoobly_agent/public/{20-es2015.473486aabfa4d4a6431b.js → 19-es2015.d0225852a844dc03a09f.js} +1 -1
  122. stoobly_agent/public/{20-es5.473486aabfa4d4a6431b.js → 19-es5.d0225852a844dc03a09f.js} +1 -1
  123. stoobly_agent/public/{3-es5.1dad290844ea619e4c16.js → 2-es2015.8f184ac63348ba447b1f.js} +1 -1
  124. stoobly_agent/public/{3-es2015.1dad290844ea619e4c16.js → 2-es5.8f184ac63348ba447b1f.js} +1 -1
  125. stoobly_agent/public/{21-es2015.56aa10803cc1348a55a3.js → 20-es2015.f7c107847935264d58aa.js} +1 -1
  126. stoobly_agent/public/{21-es5.56aa10803cc1348a55a3.js → 20-es5.f7c107847935264d58aa.js} +1 -1
  127. stoobly_agent/public/{22-es2015.46d81010003b2a50eeab.js → 21-es2015.dd358e1edaf3d32dd2c0.js} +1 -1
  128. stoobly_agent/public/{22-es5.46d81010003b2a50eeab.js → 21-es5.dd358e1edaf3d32dd2c0.js} +1 -1
  129. stoobly_agent/public/26-es2015.6332c32f1b7c8c288f2f.js +1 -0
  130. stoobly_agent/public/26-es5.6332c32f1b7c8c288f2f.js +1 -0
  131. stoobly_agent/public/27-es2015.af505e744b0c869a93d1.js +1 -0
  132. stoobly_agent/public/27-es5.af505e744b0c869a93d1.js +1 -0
  133. stoobly_agent/public/28-es2015.7c7c0f64e4af29d2e4d4.js +1 -0
  134. stoobly_agent/public/28-es5.7c7c0f64e4af29d2e4d4.js +1 -0
  135. stoobly_agent/public/32-es2015.2ab8267be7275dee9059.js +1 -0
  136. stoobly_agent/public/32-es5.2ab8267be7275dee9059.js +1 -0
  137. stoobly_agent/public/{34-es2015.ef24f6f7630620a38b19.js → 33-es2015.5b575f3a87c6c2c6b93b.js} +1 -1
  138. stoobly_agent/public/{34-es5.ef24f6f7630620a38b19.js → 33-es5.5b575f3a87c6c2c6b93b.js} +1 -1
  139. stoobly_agent/public/{35-es2015.0667e742725cc828f59f.js → 34-es2015.6a1160649c718cdb9338.js} +1 -1
  140. stoobly_agent/public/{35-es5.0667e742725cc828f59f.js → 34-es5.6a1160649c718cdb9338.js} +1 -1
  141. stoobly_agent/public/35-es2015.1b9dc7a46a6d5296c3ae.js +1 -0
  142. stoobly_agent/public/35-es5.1b9dc7a46a6d5296c3ae.js +1 -0
  143. stoobly_agent/public/{37-es2015.50d0c2d3fe4d0a74fc8f.js → 36-es2015.2fc9151fe6a5ff2bff31.js} +1 -1
  144. stoobly_agent/public/{37-es5.50d0c2d3fe4d0a74fc8f.js → 36-es5.2fc9151fe6a5ff2bff31.js} +1 -1
  145. stoobly_agent/public/{38-es2015.c14bde0b8d0cc14e915e.js → 37-es2015.ac7108c3625fd6e1d981.js} +1 -1
  146. stoobly_agent/public/{38-es5.c14bde0b8d0cc14e915e.js → 37-es5.ac7108c3625fd6e1d981.js} +1 -1
  147. stoobly_agent/public/38-es2015.9c183b14373c0e449932.js +1 -0
  148. stoobly_agent/public/38-es5.9c183b14373c0e449932.js +1 -0
  149. stoobly_agent/public/39-es2015.4624cdeb29fe9850b216.js +1 -0
  150. stoobly_agent/public/39-es5.4624cdeb29fe9850b216.js +1 -0
  151. stoobly_agent/public/40-es2015.24d981230c0c8f369cde.js +1 -0
  152. stoobly_agent/public/40-es5.24d981230c0c8f369cde.js +1 -0
  153. stoobly_agent/public/{46-es2015.abc7e4fd207d54277fcb.js → 45-es2015.c76937ed45d460bcd36f.js} +1 -1
  154. stoobly_agent/public/{46-es5.abc7e4fd207d54277fcb.js → 45-es5.c76937ed45d460bcd36f.js} +1 -1
  155. stoobly_agent/public/6-es2015.53acc5d2ca7f48ef857f.js +1 -0
  156. stoobly_agent/public/6-es5.53acc5d2ca7f48ef857f.js +1 -0
  157. stoobly_agent/public/7-es2015.1c6b3d315d50ccd228cb.js +1 -0
  158. stoobly_agent/public/7-es5.1c6b3d315d50ccd228cb.js +1 -0
  159. stoobly_agent/public/{9-es2015.ef0f7cb32f5fadb085d0.js → 8-es2015.0fe7492f7b61eb4699b6.js} +1 -1
  160. stoobly_agent/public/{9-es5.ef0f7cb32f5fadb085d0.js → 8-es5.0fe7492f7b61eb4699b6.js} +1 -1
  161. stoobly_agent/public/common-es2015.86f70de6df2c705a87f6.js +1 -0
  162. stoobly_agent/public/common-es5.86f70de6df2c705a87f6.js +1 -0
  163. stoobly_agent/public/dashboard.agent-alpha-0.22.3.tar.gz +0 -0
  164. stoobly_agent/public/index.html +1 -1
  165. stoobly_agent/public/main-es2015.aceb967cb4cccc0bc521.js +1 -0
  166. stoobly_agent/public/main-es5.aceb967cb4cccc0bc521.js +1 -0
  167. stoobly_agent/public/{polyfills-es2015.2b40b2ecdf98a9210572.js → polyfills-es2015.580f7a6e775c2c348c9d.js} +1 -1
  168. stoobly_agent/public/{polyfills-es5.d9fb2eee68607c3f7f64.js → polyfills-es5.4c3461a071d35be3dd81.js} +1 -1
  169. stoobly_agent/public/runtime-es2015.49eaebd2913fa7fe2b97.js +1 -0
  170. stoobly_agent/public/runtime-es5.49eaebd2913fa7fe2b97.js +1 -0
  171. stoobly_agent/test/test_helper.py +4 -5
  172. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/METADATA +1 -1
  173. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/RECORD +186 -168
  174. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/WHEEL +1 -1
  175. stoobly_agent/app/models/adapters/mitmproxy_request_adapter.py +0 -60
  176. stoobly_agent/app/models/adapters/types/__init__.py +0 -3
  177. stoobly_agent/public/11-es2015.b85bdc528bab0ee542fe.js +0 -1
  178. stoobly_agent/public/11-es5.b85bdc528bab0ee542fe.js +0 -1
  179. stoobly_agent/public/12-es2015.72399d40488de533bb97.js +0 -1
  180. stoobly_agent/public/12-es5.72399d40488de533bb97.js +0 -1
  181. stoobly_agent/public/14-es2015.7cedfd0829bd7a64677a.js +0 -1
  182. stoobly_agent/public/14-es5.7cedfd0829bd7a64677a.js +0 -1
  183. stoobly_agent/public/17-es2015.f6f28ba9f681063632a8.js +0 -1
  184. stoobly_agent/public/17-es5.f6f28ba9f681063632a8.js +0 -1
  185. stoobly_agent/public/18-es2015.b0cd6822ebd1090b0d60.js +0 -1
  186. stoobly_agent/public/18-es5.b0cd6822ebd1090b0d60.js +0 -1
  187. stoobly_agent/public/27-es2015.4635450ff709f7868796.js +0 -1
  188. stoobly_agent/public/27-es5.4635450ff709f7868796.js +0 -1
  189. stoobly_agent/public/28-es2015.1b29b35529772ab108f1.js +0 -1
  190. stoobly_agent/public/28-es5.1b29b35529772ab108f1.js +0 -1
  191. stoobly_agent/public/32-es2015.862b67803e6242451976.js +0 -1
  192. stoobly_agent/public/32-es5.862b67803e6242451976.js +0 -1
  193. stoobly_agent/public/33-es2015.4ff4325d1aec37e1b43c.js +0 -1
  194. stoobly_agent/public/33-es5.4ff4325d1aec37e1b43c.js +0 -1
  195. stoobly_agent/public/36-es2015.cd370fdf8990019d0c8e.js +0 -1
  196. stoobly_agent/public/36-es5.cd370fdf8990019d0c8e.js +0 -1
  197. stoobly_agent/public/39-es2015.4ec5fc16202c4759eac4.js +0 -1
  198. stoobly_agent/public/39-es5.4ec5fc16202c4759eac4.js +0 -1
  199. stoobly_agent/public/40-es2015.25287ce77b40050f3471.js +0 -1
  200. stoobly_agent/public/40-es5.25287ce77b40050f3471.js +0 -1
  201. stoobly_agent/public/45-es2015.fd110741ac0fbaada177.js +0 -1
  202. stoobly_agent/public/45-es5.fd110741ac0fbaada177.js +0 -1
  203. stoobly_agent/public/6-es2015.7219d596e3545ebaed3a.js +0 -1
  204. stoobly_agent/public/6-es5.7219d596e3545ebaed3a.js +0 -1
  205. stoobly_agent/public/7-es2015.98b085349ebd9d246060.js +0 -1
  206. stoobly_agent/public/7-es5.98b085349ebd9d246060.js +0 -1
  207. stoobly_agent/public/8-es2015.335fd5c122ad083aec65.js +0 -1
  208. stoobly_agent/public/8-es5.335fd5c122ad083aec65.js +0 -1
  209. stoobly_agent/public/common-es2015.81f870bf87411a04446d.js +0 -1
  210. stoobly_agent/public/common-es5.81f870bf87411a04446d.js +0 -1
  211. stoobly_agent/public/dashboard.agent-alpha-0.21.0.tar.gz +0 -0
  212. stoobly_agent/public/main-es2015.575c1d17fc866d3a6649.js +0 -1
  213. stoobly_agent/public/main-es5.575c1d17fc866d3a6649.js +0 -1
  214. stoobly_agent/public/runtime-es2015.8915d042dc9b55368999.js +0 -1
  215. stoobly_agent/public/runtime-es5.8915d042dc9b55368999.js +0 -1
  216. /stoobly_agent/app/models/adapters/{local_db → stoobly}/__init__.py +0 -0
  217. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/body_adapter.py +0 -0
  218. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/query_param_adapter.py +0 -0
  219. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/response_adapter.py +0 -0
  220. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/response_header_adapter.py +0 -0
  221. /stoobly_agent/app/models/{adapters/types → types}/replayed_response.py +0 -0
  222. /stoobly_agent/app/models/{adapters/types → types}/request_create_params.py +0 -0
  223. /stoobly_agent/app/models/{adapters/types → types}/request_show_params.py +0 -0
  224. /stoobly_agent/app/models/{adapters/types → types}/scenario_create_params.py +0 -0
  225. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/LICENSE +0 -0
  226. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/entry_points.txt +0 -0
  227. {stoobly_agent-0.21.0.dist-info → stoobly_agent-0.22.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ import abc
2
+
3
+ from mitmproxy.http import Request
4
+ from typing import Union
5
+
6
+ from stoobly_agent.app.proxy.test.helpers.endpoint_facade import EndpointFacade
7
+ from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
8
+
9
+ from stoobly_agent.lib.api.interfaces.endpoints import EndpointShowResponse
10
+ from stoobly_agent.lib.orm.trace import Trace
11
+
12
+ from .context_response import TestContextResponse
13
+
14
+ FuzzyContent = Union[dict, list, str]
15
+
16
+ class TestContextABC(abc.ABC):
17
+
18
+ @abc.abstractmethod
19
+ def with_endpoints_resource(self, resource: EndpointShowResponse):
20
+ pass
21
+
22
+ @property
23
+ @abc.abstractmethod
24
+ def cached_expected_response_content(self) -> Union[bytes, None, str]:
25
+ pass
26
+
27
+ @property
28
+ @abc.abstractmethod
29
+ def endpoint(self) -> EndpointFacade:
30
+ pass
31
+
32
+ @property
33
+ @abc.abstractmethod
34
+ def lifecycle_hooks(self):
35
+ pass
36
+
37
+ @property
38
+ @abc.abstractmethod
39
+ def lifecycle_hooks_script_path(self):
40
+ pass
41
+
42
+ @property
43
+ @abc.abstractmethod
44
+ def end_time(self):
45
+ pass
46
+
47
+ @property
48
+ @abc.abstractmethod
49
+ def expected_response(self) -> TestContextResponse:
50
+ pass
51
+
52
+ @property
53
+ @abc.abstractmethod
54
+ def decoded_response_content(self) -> FuzzyContent:
55
+ pass
56
+
57
+ @property
58
+ @abc.abstractmethod
59
+ def decoded_expected_response_content(self) -> FuzzyContent:
60
+ pass
61
+
62
+ @property
63
+ @abc.abstractmethod
64
+ def filter(self):
65
+ pass
66
+
67
+ @property
68
+ @abc.abstractmethod
69
+ def flow(self):
70
+ pass
71
+
72
+ @property
73
+ @abc.abstractmethod
74
+ def intercept_settings(self):
75
+ pass
76
+
77
+ @property
78
+ @abc.abstractmethod
79
+ def log(self):
80
+ pass
81
+
82
+ @log.setter
83
+ @abc.abstractmethod
84
+ def log(self, v: str):
85
+ pass
86
+
87
+ @property
88
+ @abc.abstractmethod
89
+ def mock_request_id(self) -> Union[str, None]:
90
+ pass
91
+
92
+ @property
93
+ @abc.abstractmethod
94
+ def mock_request_endpoint_id(self) -> Union[str, None]:
95
+ pass
96
+
97
+ @property
98
+ @abc.abstractmethod
99
+ def passed(self):
100
+ pass
101
+
102
+ @passed.setter
103
+ @abc.abstractmethod
104
+ def passed(self, v):
105
+ pass
106
+
107
+ @property
108
+ @abc.abstractmethod
109
+ def request(self) -> Request:
110
+ pass
111
+
112
+ @property
113
+ @abc.abstractmethod
114
+ def request_headers(self):
115
+ pass
116
+
117
+ @property
118
+ @abc.abstractmethod
119
+ def response(self) -> TestContextResponse:
120
+ pass
121
+
122
+ @property
123
+ @abc.abstractmethod
124
+ def response_param_names(self) -> RequestComponentNamesFacade:
125
+ pass
126
+
127
+ @property
128
+ @abc.abstractmethod
129
+ def rewritten_expected_response_content(self):
130
+ pass
131
+
132
+ @property
133
+ @abc.abstractmethod
134
+ def skipped(self):
135
+ pass
136
+
137
+ @skipped.setter
138
+ @abc.abstractmethod
139
+ def skipped(self, v: bool):
140
+ pass
141
+
142
+ @property
143
+ @abc.abstractmethod
144
+ def start_time(self):
145
+ pass
146
+
147
+ @property
148
+ @abc.abstractmethod
149
+ def strategy(self):
150
+ pass
151
+
152
+ @property
153
+ @abc.abstractmethod
154
+ def trace(self) -> Union[Trace, None]:
155
+ pass
@@ -2,7 +2,7 @@ import pdb
2
2
 
3
3
  from typing import Iterable, List, TypedDict, Union
4
4
 
5
- from stoobly_agent.app.proxy.test.context import TestContext
5
+ from stoobly_agent.app.proxy.test.context_abc import TestContextABC as TestContext
6
6
  from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
7
7
  from stoobly_agent.lib.api.interfaces.endpoints import RequestComponentName, ResponseParamName
8
8
  from stoobly_agent.lib.utils.python_to_ruby_type import convert
@@ -5,7 +5,7 @@ from typing import List, Tuple, TypedDict, Union
5
5
  from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
6
6
  from stoobly_agent.lib.api.interfaces.endpoints import RequestComponentName
7
7
 
8
- from ..context import TestContext
8
+ from ..context_abc import TestContextABC as TestContext
9
9
  from .context import MatchContext, build_match_context
10
10
  from .errors import length_match_error, param_name_exists_error, param_name_missing_error, type_match_error, valid_type_error
11
11
 
@@ -1,4 +1,4 @@
1
- from stoobly_agent.app.proxy.test.context import TestContext
1
+ from stoobly_agent.app.proxy.test.context_abc import TestContextABC as TestContext
2
2
 
3
3
  def matches(context: TestContext):
4
4
  lifecycle_hooks = context.lifecycle_hooks
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
 
3
3
  from typing import Tuple
4
- from stoobly_agent.app.proxy.test.context import TestContext
4
+ from stoobly_agent.app.proxy.test.context_abc import TestContextABC as TestContext
5
5
 
6
6
  from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
7
7
  from stoobly_agent.app.proxy.test.matchers.errors import length_match_error, param_name_missing_error, type_match_error, value_match_error
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
 
3
3
  from typing import Iterable, Tuple
4
- from stoobly_agent.app.proxy.test.context import TestContext
4
+ from stoobly_agent.app.proxy.test.context_abc import TestContextABC as TestContext
5
5
  from stoobly_agent.app.proxy.test.helpers.request_component_names_facade import RequestComponentNamesFacade
6
6
  from stoobly_agent.app.proxy.test.matchers.errors import param_name_missing_error, type_match_error, valid_type_error
7
7
 
@@ -4,7 +4,7 @@ from stoobly_agent.app.proxy.replay.body_parser_service import decode_response,
4
4
 
5
5
  from stoobly_agent.config.constants import test_strategy
6
6
 
7
- from .context import TestContext
7
+ from .context_abc import TestContextABC as TestContext
8
8
  from .matchers.custom import matches as custom_matches
9
9
  from .matchers.contract import matches as contract_matches
10
10
  from .matchers.diff import matches as diff_matches
@@ -53,5 +53,4 @@ class JoinedRequest:
53
53
  response_string = self.__response_string.get(control=True)
54
54
 
55
55
  joined = self.REQUEST_DELIMITTER.join([request_string, response_string])
56
-
57
56
  return joined
@@ -1,7 +1,7 @@
1
1
  from ..mitmproxy.request_facade import MitmproxyRequestFacade
2
2
 
3
3
  class ProxyRequest:
4
- def __init__(self, request: MitmproxyRequestFacade, upstream_url: str):
4
+ def __init__(self, request: MitmproxyRequestFacade, upstream_url: str = None):
5
5
  self.request = request
6
6
 
7
7
  self.upstream_url = upstream_url
@@ -9,4 +9,7 @@ class ProxyRequest:
9
9
  def url(self):
10
10
  url = self.request.url
11
11
 
12
+ if not self.upstream_url:
13
+ return url
14
+
12
15
  return url.replace(self.request.base_url, self.upstream_url)
@@ -74,7 +74,7 @@ class RequestString:
74
74
  toks = header.split('_')
75
75
 
76
76
  for index, tok in enumerate(toks):
77
- toks[index] = tok.lower().capitalize()
77
+ toks[index] = tok.lower().title()
78
78
 
79
79
  return "-".join(toks)
80
80
 
@@ -0,0 +1,3 @@
1
+ TEST = 'test'
2
+ DEVELOPMENT = 'development'
3
+ PRODUCTION = 'production'
@@ -14,7 +14,9 @@ class DataSettings:
14
14
  self.__data_rules_map = {k: DataRules(v) for k, v in self.__data_settings.items()}
15
15
 
16
16
  def data_rules(self, project_id: str) -> DataRules:
17
- return self.__data_rules_map.get(project_id) or DataRules({})
17
+ if not self.__data_rules_map.get(project_id):
18
+ self.__data_rules_map[project_id] = DataRules({})
19
+ return self.__data_rules_map[project_id]
18
20
 
19
21
  def to_dict(self):
20
22
  return {k: v.to_dict() for k, v in self.__data_rules_map.items()}
stoobly_agent/cli.py CHANGED
@@ -1,14 +1,21 @@
1
1
  import click
2
2
  import os
3
3
  import pdb
4
+ import requests
4
5
  import sys
5
6
 
6
- from stoobly_agent.config.constants import env_vars
7
+ from stoobly_agent import VERSION
8
+ from stoobly_agent.app.cli.helpers.context import ReplayContext
9
+ from stoobly_agent.app.cli.helpers.handle_mock_service import print_raw_response, RAW_FORMAT
10
+ from stoobly_agent.app.proxy.constants import custom_response_codes
11
+ from stoobly_agent.app.proxy.replay.replay_request_service import replay as replay_request
12
+ from stoobly_agent.config.constants import env_vars, mode
13
+ from stoobly_agent.config.data_dir import DataDir
7
14
  from stoobly_agent.lib.utils.conditional_decorator import ConditionalDecorator
8
15
 
9
16
  from .app.api import run as run_api
10
- from .app.cli import ca_cert, config, feature, intercept, MainGroup, project, request, trace
11
- from .app.proxy import CONNECTION_STRATEGIES, INTERCEPT_MODES, run as run_proxy
17
+ from .app.cli import ca_cert, config, feature, intercept, MainGroup, request, scenario, trace
18
+ from .app.proxy import CONNECTION_STRATEGIES, run as run_proxy
12
19
  from .app.settings import Settings
13
20
  from .lib import logger
14
21
  from .lib.orm.migrate_service import migrate as migrate_database
@@ -16,6 +23,9 @@ from .lib.orm.migrate_service import migrate as migrate_database
16
23
  settings = Settings.instance()
17
24
  is_remote = settings.cli.features.remote
18
25
 
26
+ # Makes sure database is up to date
27
+ migrate_database(VERSION)
28
+
19
29
  CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
20
30
 
21
31
  @click.version_option()
@@ -34,6 +44,7 @@ main.add_command(config)
34
44
  main.add_command(feature)
35
45
  main.add_command(intercept)
36
46
  main.add_command(request)
47
+ main.add_command(scenario)
37
48
  main.add_command(trace)
38
49
 
39
50
  if settings.cli.features.dev_tools:
@@ -45,10 +56,15 @@ if settings.cli.features.exec:
45
56
  ExecDecorator(main).decorate()
46
57
 
47
58
  if settings.cli.features.remote:
48
- from .app.cli import report, scenario
59
+ from .app.cli import project, report
49
60
  main.add_command(project)
50
61
  main.add_command(report)
51
- main.add_command(scenario)
62
+
63
+ @main.command(
64
+ help="Initialize a new context"
65
+ )
66
+ def init(**kwargs):
67
+ DataDir.instance().create()
52
68
 
53
69
  @main.command(
54
70
  help="Run proxy and/or UI",
@@ -56,7 +72,6 @@ if settings.cli.features.remote:
56
72
  @ConditionalDecorator(lambda f: click.option('--api-url', help='API URL.')(f), is_remote)
57
73
  @click.option('--headless', is_flag=True, default=False, help='Disable starting UI.')
58
74
  @click.option('--connection-strategy', help=', '.join(CONNECTION_STRATEGIES), type=click.Choice(CONNECTION_STRATEGIES))
59
- @click.option('--intercept-mode', help=', '.join(INTERCEPT_MODES), type=click.Choice(INTERCEPT_MODES))
60
75
  @click.option('--log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='''
61
76
  Log levels can be "debug", "info", "warning", or "error"
62
77
  ''')
@@ -83,10 +98,73 @@ def run(**kwargs):
83
98
  if 'headless' in kwargs and not kwargs['headless']:
84
99
  run_api(**kwargs)
85
100
 
86
- if kwargs['intercept_mode'] and kwargs['intercept_mode'] not in INTERCEPT_MODES:
87
- print(f"Error: Invalid value for --intercept-mode, values: {', '.join(INTERCEPT_MODES)}", file=sys.stderr)
88
- sys.exit(1)
101
+ run_proxy(**kwargs)
89
102
 
90
- migrate_database()
103
+ @main.command(
104
+ help="Mock request"
105
+ )
106
+ @click.option('-d', '--data', default='', help='HTTP POST data')
107
+ @click.option('--format', type=click.Choice([RAW_FORMAT]), help='Format response')
108
+ @click.option('-H', '--header', multiple=True, help='Pass custom header(s) to server')
109
+ @ConditionalDecorator(lambda f: click.option('--project-key')(f), is_remote)
110
+ @click.option('-X', '--request', default='GET', help='Specify request command to use')
111
+ @click.option('--scenario-key')
112
+ @click.argument('url')
113
+ def mock(**kwargs):
114
+ request = __build_request_from_curl(**kwargs)
115
+
116
+ context = ReplayContext.from_python_request(request)
117
+ response: requests.Response = replay_request(context, {
118
+ **kwargs,
119
+ 'mode': mode.MOCK,
120
+ })
121
+
122
+ if response.status_code == custom_response_codes.NOT_FOUND:
123
+ print(f"Error: {response.content.decode()}")
124
+ sys.exit(1)
125
+
126
+ if kwargs['format'] == RAW_FORMAT:
127
+ print_raw_response(response)
128
+ else:
129
+ print(response.content.decode())
91
130
 
92
- run_proxy(**kwargs)
131
+ @main.command(
132
+ help="Record request"
133
+ )
134
+ @click.option('-d', '--data', default='', help='HTTP POST data')
135
+ @click.option('--format', type=click.Choice([RAW_FORMAT]), help='Format response')
136
+ @click.option('-H', '--header', multiple=True, help='Pass custom header(s) to server')
137
+ @ConditionalDecorator(lambda f: click.option('--project-key')(f), is_remote)
138
+ @click.option('-X', '--request', default='GET', help='Specify request command to use')
139
+ @click.option('--scenario-key')
140
+ @click.argument('url')
141
+ def record(**kwargs):
142
+ request = __build_request_from_curl(**kwargs)
143
+
144
+ context = ReplayContext.from_python_request(request)
145
+ response: requests.Response = replay_request(context, {
146
+ **kwargs,
147
+ 'mode': mode.RECORD,
148
+ })
149
+
150
+ if kwargs['format'] == RAW_FORMAT:
151
+ print_raw_response(response)
152
+ else:
153
+ print(response.content.decode())
154
+
155
+ def __build_request_from_curl(**kwargs):
156
+ headers = {}
157
+ for header in kwargs['header']:
158
+ toks = header.split(':')
159
+
160
+ if len(toks) != 2:
161
+ continue
162
+
163
+ headers[toks[0]] = toks[1]
164
+
165
+ return requests.Request(
166
+ data=kwargs['data'],
167
+ headers=headers,
168
+ method=kwargs['request'],
169
+ url=kwargs['url']
170
+ )
@@ -1,8 +1,9 @@
1
1
  AGENT_ACTIVE_MODE = 'STOOBLY_AGENT_ACTIVE_MODE'
2
2
  AGENT_CONFIG_PATH = 'STOOBLY_AGENT_CONFIG_PATH'
3
3
  AGENT_ENABLED = 'STOOBLY_AGENT_ENABLED'
4
- AGENT_IS_HEADLESS = 'STOOBLY_AGENT_IS_HEADLESS'
4
+ AGENT_SELF_INTERCEPT_ENABLED = 'STOOBLY_AGENT_SELF_INTERCEPT_ENABLED'
5
5
  AGENT_INCLUDE_PATTERNS = 'STOOBLY_AGENT_INCLUDE_PATTERNS'
6
+ AGENT_IS_HEADLESS = 'STOOBLY_AGENT_IS_HEADLESS'
6
7
  AGENT_EXCLUDE_PATTERNS = 'STOOBLY_AGENT_EXCLUDE_PATTERNS'
7
8
  AGENT_POLICY = 'STOOBLY_AGENT_POLICY'
8
9
  AGENT_PROJECT_KEY = 'STOOBLY_AGENT_PROJECT_KEY'
@@ -1,10 +1,12 @@
1
1
  import os
2
2
  import pdb
3
- import tempfile
3
+
4
+ from stoobly_agent.config.constants.env_vars import ENV
4
5
 
5
6
  class DataDir:
6
7
  DATA_DIR_NAME = '.stoobly'
7
8
  DB_FILE_NAME = 'stoobly_agent.sqlite3'
9
+ DB_VERSION_NAME = 'VERSION'
8
10
 
9
11
  _instance = None
10
12
 
@@ -12,11 +14,15 @@ class DataDir:
12
14
  if DataDir._instance:
13
15
  raise RuntimeError('Call instance() instead')
14
16
  else:
15
- self.__data_dir_path = os.path.join(os.path.expanduser('~'), self.DATA_DIR_NAME)
17
+ self.__data_dir_path = os.path.join(os.getcwd(), self.DATA_DIR_NAME)
16
18
 
19
+ # If the current working directory does not contain a .stoobly folder, default to home directory
17
20
  if not os.path.exists(self.__data_dir_path):
18
- os.mkdir(self.__data_dir_path)
21
+ self.__data_dir_path = os.path.join(os.path.expanduser('~'), self.DATA_DIR_NAME)
19
22
 
23
+ if not os.path.exists(self.__data_dir_path):
24
+ os.makedirs(self.__data_dir_path, exist_ok=True)
25
+
20
26
  @classmethod
21
27
  def instance(cls):
22
28
  if cls._instance is None:
@@ -26,6 +32,13 @@ class DataDir:
26
32
 
27
33
  @property
28
34
  def path(self):
35
+ if os.environ.get(ENV) == 'test':
36
+ test_path = os.path.join(self.__data_dir_path, 'tmp', self.DATA_DIR_NAME)
37
+
38
+ if not os.path.exists(test_path):
39
+ os.makedirs(test_path, exist_ok=True)
40
+ return test_path
41
+
29
42
  return self.__data_dir_path
30
43
 
31
44
  @property
@@ -46,15 +59,23 @@ class DataDir:
46
59
 
47
60
  return db_dir_path
48
61
 
49
- @property
50
- def tmp_db_file_path(self):
51
- tmp = tempfile.NamedTemporaryFile(delete=False)
52
- return tmp.name
53
-
54
62
  @property
55
63
  def db_file_path(self):
56
64
  return os.path.join(self.db_dir_path, self.DB_FILE_NAME)
57
65
 
66
+ @property
67
+ def db_version_path(self):
68
+ return os.path.join(self.db_dir_path, self.DB_VERSION_NAME)
69
+
58
70
  @property
59
71
  def settings_file_path(self):
60
72
  return os.path.join(self.path, 'settings.yml')
73
+
74
+ def create(self, directoy_path = None):
75
+ if not directoy_path:
76
+ directoy_path = os.getcwd()
77
+
78
+ self.__data_dir_path = os.path.join(directoy_path, self.DATA_DIR_NAME)
79
+
80
+ if not os.path.exists(self.__data_dir_path):
81
+ os.mkdir(self.__data_dir_path)
@@ -9,7 +9,7 @@ proxy:
9
9
  intercept:
10
10
  active: false
11
11
  mode: record
12
- project_key: ''
12
+ project_key: eyJpIjowLCJvIjowfQ==
13
13
  firewall: {}
14
14
  rewrite: {}
15
15
  proxy_config_path: config/settings.yml
@@ -26,14 +26,14 @@ class AlignRequests(Migration):
26
26
  _request: requests.Request = RawHttpRequestAdapter(request.raw)
27
27
 
28
28
  parsed_url = urlparse(_request.url)
29
- request.path = parsed_url.path.decode()
30
- request.query = parsed_url.query.decode()
29
+ request.path = parsed_url.path
30
+ request.query = parsed_url.query
31
31
 
32
32
  if parsed_url.username:
33
- request.user = parsed_url.username.decode()
33
+ request.user = parsed_url.username
34
34
 
35
35
  if parsed_url.password:
36
- request.password = parsed_url.password.decode()
36
+ request.password = parsed_url.password
37
37
 
38
38
  response = request.response
39
39
  if response:
@@ -44,16 +44,16 @@ class Api():
44
44
  def set_proxy(self, val):
45
45
  current = {}
46
46
 
47
- current[HTTP_PROXY] = os.environ.get(HTTP_PROXY)
47
+ current[HTTP_PROXY] = os.environ.get(HTTP_PROXY) or ''
48
48
  os.environ[HTTP_PROXY] = val
49
49
 
50
- current[HTTPS_PROXY] = os.environ.get(HTTPS_PROXY)
50
+ current[HTTPS_PROXY] = os.environ.get(HTTPS_PROXY) or ''
51
51
  os.environ[HTTPS_PROXY] = val
52
52
 
53
- current[HTTP_PROXY.lower()] = os.environ.get(HTTP_PROXY.lower())
53
+ current[HTTP_PROXY.lower()] = os.environ.get(HTTP_PROXY.lower()) or ''
54
54
  os.environ[HTTP_PROXY.lower()] = val
55
55
 
56
- current[HTTPS_PROXY.lower()] = os.environ.get(HTTPS_PROXY.lower())
56
+ current[HTTPS_PROXY.lower()] = os.environ.get(HTTPS_PROXY.lower()) or ''
57
57
  os.environ[HTTPS_PROXY.lower()] = val
58
58
 
59
59
  return current
@@ -1,5 +1,7 @@
1
1
  from .resource_key import ResourceKey
2
2
 
3
+ LOCAL_ORGANIZATION_ID = 0
4
+
3
5
  class InvalidOrganizationKey(Exception):
4
6
  pass
5
7
 
@@ -1,4 +1,5 @@
1
1
  from .resource_key import ResourceKey
2
+ from .organization_key import LOCAL_ORGANIZATION_ID
2
3
 
3
4
  LOCAL_PROJECT_ID = 0
4
5
 
@@ -16,6 +17,18 @@ class ProjectKey(ResourceKey):
16
17
  if not self.organization_id:
17
18
  raise InvalidProjectKey('Missing organization_id')
18
19
 
20
+ @property
21
+ @classmethod
22
+ def local_key(cls) -> str:
23
+ return cls.encode({
24
+ 'i': LOCAL_PROJECT_ID,
25
+ 'o': LOCAL_ORGANIZATION_ID,
26
+ })
27
+
28
+ @property
29
+ def is_local(self) -> bool:
30
+ return int(self.id) == LOCAL_PROJECT_ID
31
+
19
32
  @property
20
33
  def id(self) -> str:
21
34
  return self.get('i')
@@ -1,11 +1,11 @@
1
1
  import base64
2
2
  import json
3
- import random
4
- import time
5
3
 
6
4
  class ResourceKey():
7
5
 
8
6
  def __init__(self, key):
7
+ self.__raw = key
8
+
9
9
  try:
10
10
  key = base64.b64decode(key)
11
11
  except:
@@ -19,6 +19,9 @@ class ResourceKey():
19
19
  except:
20
20
  self.decoded_key = {}
21
21
 
22
+ @property
23
+ def raw(self):
24
+ return self.__raw
22
25
 
23
26
  @staticmethod
24
27
  def encode(data: dict):
@@ -26,7 +29,6 @@ class ResourceKey():
26
29
  **data,
27
30
  }).encode())
28
31
 
29
-
30
32
  def get(self, k: str):
31
33
  v = self.decoded_key.get(k)
32
34
  return str(v) if v != None else None
@@ -1,6 +1,8 @@
1
1
  import requests
2
2
  import urllib
3
3
 
4
+ from stoobly_agent.app.models.types import ScenarioCreateParams
5
+
4
6
  from ..logger import Logger
5
7
  from .interfaces.pagination_query_params import PaginationQueryParams
6
8
  from .stoobly_api import StooblyApi
@@ -8,19 +10,9 @@ from .stoobly_api import StooblyApi
8
10
  class ScenariosResource(StooblyApi):
9
11
  SCENARIOS_ENDPOINT = 'scenarios'
10
12
 
11
- def create(self, project_id: str, params, raw_requests = ''):
13
+ def create(self, **params: ScenarioCreateParams):
12
14
  url = f"{self.service_url}/{self.SCENARIOS_ENDPOINT}"
13
-
14
- body = {
15
- 'project_id': project_id,
16
- **params,
17
- }
18
-
19
- files = {}
20
- if len(raw_requests) > 0:
21
- files = { 'file': raw_requests }
22
-
23
- return self.post(url, headers=self.default_headers, data=body, files=files)
15
+ return self.post(url, headers=self.default_headers, data=params)
24
16
 
25
17
  def index(self, **query_params: PaginationQueryParams) -> requests.Response:
26
18
  url = f"{self.service_url}/{self.SCENARIOS_ENDPOINT}"
@@ -1,10 +1,10 @@
1
1
  import base64
2
2
  import json
3
- import requests
4
- import urllib
3
+ import os
5
4
  import pdb
6
5
 
7
- from ..logger import Logger
6
+ from stoobly_agent.config.constants import custom_headers, env_vars
7
+
8
8
  from .api import Api
9
9
 
10
10
  class StooblyApi(Api):
@@ -71,11 +71,15 @@ class StooblyApi(Api):
71
71
 
72
72
  @property
73
73
  def default_headers(self):
74
- return {
74
+ headers = {
75
75
  'X-API-KEY': self.api_key,
76
- 'X-Do-Proxy': '1',
77
76
  }
78
77
 
78
+ if not os.environ.get(env_vars.AGENT_SELF_INTERCEPT_ENABLED):
79
+ headers[custom_headers.DO_PROXY] = '1'
80
+
81
+ return headers
82
+
79
83
  # Request
80
84
 
81
85
  def from_project_key(self, project_key, handler):