stoobly-agent 1.4.2__py3-none-any.whl → 1.5.1__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +2 -2
- stoobly_agent/app/cli/helpers/handle_mock_service.py +6 -2
- stoobly_agent/app/cli/helpers/request_facade.py +5 -1
- stoobly_agent/app/cli/scaffold/constants.py +1 -1
- stoobly_agent/app/cli/scaffold/docker/workflow/mock_decorator.py +1 -0
- stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +19 -19
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/constants.py +3 -3
- stoobly_agent/app/cli/scaffold/templates/factory.py +5 -5
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/bin/configure +1 -8
- stoobly_agent/app/cli/scaffold/templates/workflow/mock/fixtures.yml +1 -1
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure +1 -8
- stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml +1 -1
- stoobly_agent/app/cli/scaffold/workflow_command.py +3 -3
- stoobly_agent/app/cli/scaffold/workflow_create_command.py +2 -2
- stoobly_agent/app/cli/scaffold_cli.py +5 -5
- stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py +1 -1
- stoobly_agent/app/models/factories/resource/local_db/request_adapter.py +17 -11
- stoobly_agent/app/models/types/request.py +1 -2
- stoobly_agent/app/proxy/context.py +4 -0
- stoobly_agent/app/proxy/handle_mock_service.py +93 -46
- stoobly_agent/app/proxy/handle_record_service.py +15 -3
- stoobly_agent/app/proxy/handle_replay_service.py +44 -18
- stoobly_agent/app/proxy/handle_test_service.py +92 -24
- stoobly_agent/app/proxy/intercept_handler.py +11 -16
- stoobly_agent/app/proxy/intercept_settings.py +17 -4
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +5 -2
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +5 -4
- stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py +5 -0
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +79 -14
- stoobly_agent/app/proxy/mock/eval_request_service.py +18 -13
- stoobly_agent/app/proxy/record/join_request_service.py +7 -8
- stoobly_agent/app/proxy/record/upload_request_service.py +2 -2
- stoobly_agent/app/proxy/replay/replay_request_service.py +4 -4
- stoobly_agent/app/proxy/test/helpers/upload_test_service.py +2 -2
- stoobly_agent/app/proxy/utils/allowed_request_service.py +3 -3
- stoobly_agent/app/proxy/utils/response_handler.py +10 -1
- stoobly_agent/app/proxy/utils/rewrite.py +72 -0
- stoobly_agent/app/settings/constants/request_component.py +4 -1
- stoobly_agent/cli.py +35 -28
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/intercept_policy.py +2 -0
- stoobly_agent/config/constants/mock_policy.py +4 -2
- stoobly_agent/config/constants/query_params.py +2 -0
- stoobly_agent/config/constants/record_policy.py +4 -2
- stoobly_agent/config/constants/replay_policy.py +4 -2
- stoobly_agent/public/{18-es2015.583f191cc7ad512ee262.js → 18-es2015.503207073756a9c8211a.js} +1 -1
- stoobly_agent/public/{18-es5.583f191cc7ad512ee262.js → 18-es5.503207073756a9c8211a.js} +1 -1
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/{main-es2015.2cc16523aa3fcaba51e5.js → main-es2015.d682619f3d6d53d64c6a.js} +1 -1
- stoobly_agent/public/{main-es5.2cc16523aa3fcaba51e5.js → main-es5.d682619f3d6d53d64c6a.js} +1 -1
- stoobly_agent/public/{runtime-es2015.b914470164e4d6e75d96.js → runtime-es2015.8c1efed946fc02c923fc.js} +1 -1
- stoobly_agent/public/{runtime-es5.b914470164e4d6e75d96.js → runtime-es5.8c1efed946fc02c923fc.js} +1 -1
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_test.py +2 -1
- stoobly_agent/test/app/cli/scaffold/e2e_test.py +2 -2
- stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py +4 -4
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +140 -71
- stoobly_agent/test/cli/lifecycle_hooks_test.py +66 -0
- stoobly_agent/test/cli/mock_scenario_lifecycle_hooks.py +5 -0
- stoobly_agent/test/cli/mock_scenario_test.py +62 -0
- stoobly_agent/test/cli/mock_test.py +54 -38
- stoobly_agent/test/cli/record_test.py +67 -0
- stoobly_agent/test/mock_data/lifecycle_hooks.py +35 -0
- {stoobly_agent-1.4.2.dist-info → stoobly_agent-1.5.1.dist-info}/LICENSE +1 -1
- {stoobly_agent-1.4.2.dist-info → stoobly_agent-1.5.1.dist-info}/METADATA +7 -12
- {stoobly_agent-1.4.2.dist-info → stoobly_agent-1.5.1.dist-info}/RECORD +72 -65
- /stoobly_agent/app/cli/scaffold/templates/workflow/mock/{fixtures/.keep → public/.gitignore} +0 -0
- /stoobly_agent/app/cli/scaffold/templates/workflow/test/{fixtures/.keep → public/.gitignore} +0 -0
- {stoobly_agent-1.4.2.dist-info → stoobly_agent-1.5.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.4.2.dist-info → stoobly_agent-1.5.1.dist-info}/entry_points.txt +0 -0
@@ -1 +1 @@
|
|
1
|
-
!function(e){function r(r){for(var a,f,o=r[0],b=r[1],d=r[2],i=0,l=[];i<o.length;i++)f=o[i],Object.prototype.hasOwnProperty.call(c,f)&&c[f]&&l.push(c[f][0]),c[f]=0;for(a in b)Object.prototype.hasOwnProperty.call(b,a)&&(e[a]=b[a]);for(u&&u(r);l.length;)l.shift()();return n.push.apply(n,d||[]),t()}function t(){for(var e,r=0;r<n.length;r++){for(var t=n[r],a=!0,o=1;o<t.length;o++)0!==c[t[o]]&&(a=!1);a&&(n.splice(r--,1),e=f(f.s=t[0]))}return e}var a={},c={11:0},n=[];function f(r){if(a[r])return a[r].exports;var t=a[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var r=[],t=c[e];if(0!==t)if(t)r.push(t[2]);else{var a=new Promise(function(r,a){t=c[e]=[r,a]});r.push(t[2]=a);var n,o=document.createElement("script");o.charset="utf-8",o.timeout=120,f.nc&&o.setAttribute("nonce",f.nc),o.src=function(e){return f.p+""+({3:"common"}[e]||e)+"-es2015."+{0:"c7c5a6d51b98cb85b9fc",1:"bb4492eeabe1fe937ada",2:"51eb8544ea9a21f2874c",3:"388805227aa99c527fba",4:"f8ac5475bf103969b2d2",5:"90ea7bd4439d9749e052",6:"5fb726c0555664300974",7:"19ccb84e62e2ea874f53",8:"b901639e2aeff1358c3d",9:"b7bcad8238f58e214f03",10:"56da22458086513ba0c7",12:"be58ed0ef449008b932e",13:"343b0261a8b3b3f4a1fc",14:"b6619d7742671d2a37fb",15:"d8855701408b0e1d7a3e",16:"98fa59c2c96d2caac3c3",17:"7f60b56d1fd66d4d5544",18:"
|
1
|
+
!function(e){function r(r){for(var a,f,o=r[0],b=r[1],d=r[2],i=0,l=[];i<o.length;i++)f=o[i],Object.prototype.hasOwnProperty.call(c,f)&&c[f]&&l.push(c[f][0]),c[f]=0;for(a in b)Object.prototype.hasOwnProperty.call(b,a)&&(e[a]=b[a]);for(u&&u(r);l.length;)l.shift()();return n.push.apply(n,d||[]),t()}function t(){for(var e,r=0;r<n.length;r++){for(var t=n[r],a=!0,o=1;o<t.length;o++)0!==c[t[o]]&&(a=!1);a&&(n.splice(r--,1),e=f(f.s=t[0]))}return e}var a={},c={11:0},n=[];function f(r){if(a[r])return a[r].exports;var t=a[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var r=[],t=c[e];if(0!==t)if(t)r.push(t[2]);else{var a=new Promise(function(r,a){t=c[e]=[r,a]});r.push(t[2]=a);var n,o=document.createElement("script");o.charset="utf-8",o.timeout=120,f.nc&&o.setAttribute("nonce",f.nc),o.src=function(e){return f.p+""+({3:"common"}[e]||e)+"-es2015."+{0:"c7c5a6d51b98cb85b9fc",1:"bb4492eeabe1fe937ada",2:"51eb8544ea9a21f2874c",3:"388805227aa99c527fba",4:"f8ac5475bf103969b2d2",5:"90ea7bd4439d9749e052",6:"5fb726c0555664300974",7:"19ccb84e62e2ea874f53",8:"b901639e2aeff1358c3d",9:"b7bcad8238f58e214f03",10:"56da22458086513ba0c7",12:"be58ed0ef449008b932e",13:"343b0261a8b3b3f4a1fc",14:"b6619d7742671d2a37fb",15:"d8855701408b0e1d7a3e",16:"98fa59c2c96d2caac3c3",17:"7f60b56d1fd66d4d5544",18:"503207073756a9c8211a",19:"78ec0e9fd228b5104712",20:"90544c488f1f0900bab4",21:"63ed4e6b242fbc047bd6",22:"1e96ef651f96d51191e2",23:"6d3052555b4fac38f16d",28:"a2bf42f38e4dd771ccea",29:"18b515f07c88753c7eb6",30:"d4d3dc4101400cadf3f3",31:"e3ea815f75c19c217b7d",32:"57553995bff0ae518501",33:"da5ba1e5baa164921198",34:"87acbb95cfb824650157",35:"8f79ff8748d4ff06ab03",36:"e3a104020eeec02cb8c4",37:"3b472e6cf303201bbf2c",38:"39dbf43d46cdc94f78c6",39:"e5475ea4fb6ec5c60741",40:"47b2873a8439717af6a7",41:"69f2771002745c066976",42:"b590b92ede890155dcd6",43:"ad1d956bffba561ba64c",44:"3ef2c17996ed5be95ced",45:"3d65d27e0a7085e96356",46:"e4f0a5b127eae13c27c0",47:"c7d45334a70131f133b5"}[e]+".js"}(e);var b=new Error;n=function(r){o.onerror=o.onload=null,clearTimeout(d);var t=c[e];if(0!==t){if(t){var a=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;b.message="Loading chunk "+e+" failed.\n("+a+": "+n+")",b.name="ChunkLoadError",b.type=a,b.request=n,t[1](b)}c[e]=void 0}};var d=setTimeout(function(){n({type:"timeout",target:o})},12e4);o.onerror=o.onload=n,document.head.appendChild(o)}return Promise.all(r)},f.m=e,f.c=a,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var a in e)f.d(t,a,(function(r){return e[r]}).bind(null,a));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="",f.oe=function(e){throw console.error(e),e};var o=window.webpackJsonp=window.webpackJsonp||[],b=o.push.bind(o);o.push=r,o=o.slice();for(var d=0;d<o.length;d++)r(o[d]);var u=b;t()}([]);
|
stoobly_agent/public/{runtime-es5.b914470164e4d6e75d96.js → runtime-es5.8c1efed946fc02c923fc.js}
RENAMED
@@ -1 +1 @@
|
|
1
|
-
!function(e){function r(r){for(var a,f,o=r[0],b=r[1],d=r[2],i=0,l=[];i<o.length;i++)f=o[i],Object.prototype.hasOwnProperty.call(c,f)&&c[f]&&l.push(c[f][0]),c[f]=0;for(a in b)Object.prototype.hasOwnProperty.call(b,a)&&(e[a]=b[a]);for(u&&u(r);l.length;)l.shift()();return n.push.apply(n,d||[]),t()}function t(){for(var e,r=0;r<n.length;r++){for(var t=n[r],a=!0,o=1;o<t.length;o++)0!==c[t[o]]&&(a=!1);a&&(n.splice(r--,1),e=f(f.s=t[0]))}return e}var a={},c={11:0},n=[];function f(r){if(a[r])return a[r].exports;var t=a[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var r=[],t=c[e];if(0!==t)if(t)r.push(t[2]);else{var a=new Promise(function(r,a){t=c[e]=[r,a]});r.push(t[2]=a);var n,o=document.createElement("script");o.charset="utf-8",o.timeout=120,f.nc&&o.setAttribute("nonce",f.nc),o.src=function(e){return f.p+""+({3:"common"}[e]||e)+"-es5."+{0:"c7c5a6d51b98cb85b9fc",1:"bb4492eeabe1fe937ada",2:"51eb8544ea9a21f2874c",3:"388805227aa99c527fba",4:"f8ac5475bf103969b2d2",5:"90ea7bd4439d9749e052",6:"5fb726c0555664300974",7:"19ccb84e62e2ea874f53",8:"b901639e2aeff1358c3d",9:"b7bcad8238f58e214f03",10:"56da22458086513ba0c7",12:"be58ed0ef449008b932e",13:"343b0261a8b3b3f4a1fc",14:"b6619d7742671d2a37fb",15:"d8855701408b0e1d7a3e",16:"98fa59c2c96d2caac3c3",17:"7f60b56d1fd66d4d5544",18:"
|
1
|
+
!function(e){function r(r){for(var a,f,o=r[0],b=r[1],d=r[2],i=0,l=[];i<o.length;i++)f=o[i],Object.prototype.hasOwnProperty.call(c,f)&&c[f]&&l.push(c[f][0]),c[f]=0;for(a in b)Object.prototype.hasOwnProperty.call(b,a)&&(e[a]=b[a]);for(u&&u(r);l.length;)l.shift()();return n.push.apply(n,d||[]),t()}function t(){for(var e,r=0;r<n.length;r++){for(var t=n[r],a=!0,o=1;o<t.length;o++)0!==c[t[o]]&&(a=!1);a&&(n.splice(r--,1),e=f(f.s=t[0]))}return e}var a={},c={11:0},n=[];function f(r){if(a[r])return a[r].exports;var t=a[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var r=[],t=c[e];if(0!==t)if(t)r.push(t[2]);else{var a=new Promise(function(r,a){t=c[e]=[r,a]});r.push(t[2]=a);var n,o=document.createElement("script");o.charset="utf-8",o.timeout=120,f.nc&&o.setAttribute("nonce",f.nc),o.src=function(e){return f.p+""+({3:"common"}[e]||e)+"-es5."+{0:"c7c5a6d51b98cb85b9fc",1:"bb4492eeabe1fe937ada",2:"51eb8544ea9a21f2874c",3:"388805227aa99c527fba",4:"f8ac5475bf103969b2d2",5:"90ea7bd4439d9749e052",6:"5fb726c0555664300974",7:"19ccb84e62e2ea874f53",8:"b901639e2aeff1358c3d",9:"b7bcad8238f58e214f03",10:"56da22458086513ba0c7",12:"be58ed0ef449008b932e",13:"343b0261a8b3b3f4a1fc",14:"b6619d7742671d2a37fb",15:"d8855701408b0e1d7a3e",16:"98fa59c2c96d2caac3c3",17:"7f60b56d1fd66d4d5544",18:"503207073756a9c8211a",19:"78ec0e9fd228b5104712",20:"90544c488f1f0900bab4",21:"63ed4e6b242fbc047bd6",22:"1e96ef651f96d51191e2",23:"6d3052555b4fac38f16d",28:"a2bf42f38e4dd771ccea",29:"18b515f07c88753c7eb6",30:"d4d3dc4101400cadf3f3",31:"e3ea815f75c19c217b7d",32:"57553995bff0ae518501",33:"da5ba1e5baa164921198",34:"87acbb95cfb824650157",35:"8f79ff8748d4ff06ab03",36:"e3a104020eeec02cb8c4",37:"3b472e6cf303201bbf2c",38:"39dbf43d46cdc94f78c6",39:"e5475ea4fb6ec5c60741",40:"47b2873a8439717af6a7",41:"69f2771002745c066976",42:"b590b92ede890155dcd6",43:"ad1d956bffba561ba64c",44:"3ef2c17996ed5be95ced",45:"3d65d27e0a7085e96356",46:"e4f0a5b127eae13c27c0",47:"c7d45334a70131f133b5"}[e]+".js"}(e);var b=new Error;n=function(r){o.onerror=o.onload=null,clearTimeout(d);var t=c[e];if(0!==t){if(t){var a=r&&("load"===r.type?"missing":r.type),n=r&&r.target&&r.target.src;b.message="Loading chunk "+e+" failed.\n("+a+": "+n+")",b.name="ChunkLoadError",b.type=a,b.request=n,t[1](b)}c[e]=void 0}};var d=setTimeout(function(){n({type:"timeout",target:o})},12e4);o.onerror=o.onload=n,document.head.appendChild(o)}return Promise.all(r)},f.m=e,f.c=a,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var a in e)f.d(t,a,(function(r){return e[r]}).bind(null,a));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="",f.oe=function(e){throw console.error(e),e};var o=window.webpackJsonp=window.webpackJsonp||[],b=o.push.bind(o);o.push=r,o=o.slice();for(var d=0;d<o.length;d++)r(o[d]);var u=b;t()}([]);
|
@@ -124,7 +124,7 @@ class TestScaffoldE2e():
|
|
124
124
|
# Add user defined Docker Compose file for the local service
|
125
125
|
shutil.copyfile(local_service_mock_docker_compose_path, destination_path)
|
126
126
|
|
127
|
-
# Record workflow doesn't have a
|
127
|
+
# Record workflow doesn't have a public folder
|
128
128
|
|
129
129
|
# Generate certs
|
130
130
|
ScaffoldCliInvoker.cli_app_mkcert(runner, app_dir_path)
|
@@ -249,7 +249,7 @@ class TestScaffoldE2e():
|
|
249
249
|
'service_name': external_service_docker_compose.service_name
|
250
250
|
}
|
251
251
|
command = ServiceWorkflowValidateCommand(app, **config)
|
252
|
-
with open(f"{command.
|
252
|
+
with open(f"{command.public_dir_path}/shared_file.txt", 'w') as file:
|
253
253
|
file.write('this is a shared file')
|
254
254
|
|
255
255
|
ScaffoldCliInvoker.cli_workflow_up(runner, app_dir_path, target_workflow_name=target_workflow_name)
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py
CHANGED
@@ -5,7 +5,7 @@ import time
|
|
5
5
|
from typing import List
|
6
6
|
|
7
7
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.tiebreak_scenario_request import (
|
8
|
-
access_request, generate_session_id,
|
8
|
+
access_request, generate_session_id, reset_sessions, tiebreak_scenario_request
|
9
9
|
)
|
10
10
|
from stoobly_agent.lib.cache import Cache
|
11
11
|
from stoobly_agent.lib.orm.request import Request
|
@@ -92,7 +92,7 @@ class TestTiebreakScenarioRequest():
|
|
92
92
|
request = tiebreak_scenario_request(session_id, requests)
|
93
93
|
assert request.id == 2
|
94
94
|
|
95
|
-
class
|
95
|
+
class TestWhenResetSessions():
|
96
96
|
@pytest.fixture(scope='class')
|
97
97
|
def created_request_one(self):
|
98
98
|
return RequestMock(1)
|
@@ -105,7 +105,7 @@ class TestTiebreakScenarioRequest():
|
|
105
105
|
def test_it_resets(self, cache: Cache, created_request_one: Request):
|
106
106
|
cache.write('persists', 1)
|
107
107
|
access_request('1', created_request_one.id)
|
108
|
-
|
108
|
+
reset_sessions()
|
109
109
|
|
110
110
|
assert cache.read('persists') != None
|
111
|
-
assert len(cache.read_all()) == 1
|
111
|
+
assert len(cache.read_all()) == 1
|
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.5.0
|
@@ -25,75 +25,144 @@ def settings():
|
|
25
25
|
return reset()
|
26
26
|
|
27
27
|
class TestEvalFixturesService():
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
'
|
75
|
-
'
|
76
|
-
|
77
|
-
|
28
|
+
class TestResponseFixtures():
|
29
|
+
@pytest.fixture(scope='class')
|
30
|
+
def request_method(self):
|
31
|
+
return 'POST'
|
32
|
+
|
33
|
+
@pytest.fixture(scope='class')
|
34
|
+
def request_url(self):
|
35
|
+
return 'https://petstore.swagger.io/404.html'
|
36
|
+
|
37
|
+
@pytest.fixture(scope='class')
|
38
|
+
def created_request(
|
39
|
+
self, settings: Settings, request_method: str, request_url: str
|
40
|
+
):
|
41
|
+
status = RequestBuilder(
|
42
|
+
method=request_method,
|
43
|
+
response_body='',
|
44
|
+
status_code=200,
|
45
|
+
url=request_url,
|
46
|
+
).with_settings(settings).build()[1]
|
47
|
+
assert status == 200
|
48
|
+
|
49
|
+
return Request.last()
|
50
|
+
|
51
|
+
@pytest.fixture(scope='class')
|
52
|
+
def not_found_file_contents(self):
|
53
|
+
return b'Not Found'
|
54
|
+
|
55
|
+
@pytest.fixture(scope='class')
|
56
|
+
def public_directory(self):
|
57
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
58
|
+
public_dir_path = os.path.join(tmp_dir_path, 'public')
|
59
|
+
if not os.path.exists(public_dir_path):
|
60
|
+
os.mkdir(public_dir_path)
|
61
|
+
return public_dir_path
|
62
|
+
|
63
|
+
@pytest.fixture(autouse=True, scope='class')
|
64
|
+
def not_found_file_path(self, public_directory: str, not_found_file_contents: str):
|
65
|
+
path = os.path.join(public_directory, '404.html')
|
66
|
+
with open(path, 'wb') as fp:
|
67
|
+
fp.write(not_found_file_contents)
|
68
|
+
return path
|
69
|
+
|
70
|
+
@pytest.fixture(scope='class')
|
71
|
+
def response_fixtures(self, request_method: str, not_found_file_path: str) -> Fixtures:
|
72
|
+
fixtures = {}
|
73
|
+
fixtures[request_method] = {
|
74
|
+
'/404.html': {
|
75
|
+
'headers': {
|
76
|
+
'test': '1',
|
77
|
+
},
|
78
|
+
'path': not_found_file_path,
|
79
|
+
'status_code': 404,
|
80
|
+
}
|
78
81
|
}
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
82
|
+
return fixtures
|
83
|
+
|
84
|
+
@pytest.fixture()
|
85
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
86
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
87
|
+
|
88
|
+
@pytest.fixture()
|
89
|
+
def fixtures_response(self, mitmproxy_request: MitmproxyRequest, response_fixtures: Fixtures):
|
90
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures=response_fixtures)
|
91
|
+
assert res != None
|
92
|
+
return res
|
93
|
+
|
94
|
+
def test_it_sets_response(
|
95
|
+
self, fixtures_response: requests.Response, not_found_file_contents: str
|
96
|
+
):
|
97
|
+
assert fixtures_response.raw.read() == not_found_file_contents
|
98
|
+
|
99
|
+
def test_it_sets_headers(self, fixtures_response: requests.Response):
|
100
|
+
assert fixtures_response.headers['test'] == '1'
|
101
|
+
assert fixtures_response.headers['Content-Type'] == 'text/html'
|
102
|
+
|
103
|
+
def test_it_sets_status_code(self, fixtures_response: requests.Response):
|
104
|
+
assert fixtures_response.status_code == 404
|
105
|
+
|
106
|
+
class TestPublicDirectory():
|
107
|
+
@pytest.fixture(scope='class')
|
108
|
+
def request_method(self):
|
109
|
+
return 'GET'
|
110
|
+
|
111
|
+
@pytest.fixture(scope='class')
|
112
|
+
def request_url(self):
|
113
|
+
return 'https://petstore.swagger.io'
|
114
|
+
|
115
|
+
@pytest.fixture(scope='class')
|
116
|
+
def created_request(
|
117
|
+
self, settings: Settings, request_method: str, request_url: str
|
118
|
+
):
|
119
|
+
status = RequestBuilder(
|
120
|
+
method=request_method,
|
121
|
+
request_headers={'accept': 'text/html;q=0.1,application/json;q=0.9'},
|
122
|
+
response_body='',
|
123
|
+
status_code=200,
|
124
|
+
url=request_url,
|
125
|
+
).with_settings(settings).build()[1]
|
126
|
+
assert status == 200
|
127
|
+
|
128
|
+
return Request.last()
|
129
|
+
|
130
|
+
@pytest.fixture()
|
131
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
132
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
133
|
+
|
134
|
+
@pytest.fixture(scope='class')
|
135
|
+
def public_directory(self):
|
136
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
137
|
+
public_dir_path = os.path.join(tmp_dir_path, 'public')
|
138
|
+
if not os.path.exists(public_dir_path):
|
139
|
+
os.mkdir(public_dir_path)
|
140
|
+
return public_dir_path
|
141
|
+
|
142
|
+
@pytest.fixture(scope='class')
|
143
|
+
def index_file_contents(self):
|
144
|
+
return b'Hello World!'
|
145
|
+
|
146
|
+
@pytest.fixture(autouse=True, scope='class')
|
147
|
+
def index_file_path(self, public_directory: str, index_file_contents: str):
|
148
|
+
path = os.path.join(public_directory, 'index.html')
|
149
|
+
with open(path, 'wb') as fp:
|
150
|
+
fp.write(index_file_contents)
|
151
|
+
return path
|
152
|
+
|
153
|
+
@pytest.fixture()
|
154
|
+
def public_directory_response(self, mitmproxy_request: MitmproxyRequest, public_directory: str):
|
155
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, public_directory_path=public_directory)
|
156
|
+
assert res != None
|
157
|
+
return res
|
158
|
+
|
159
|
+
def test_it_sets_contents(
|
160
|
+
self, public_directory_response: requests.Response, index_file_contents: str
|
161
|
+
):
|
162
|
+
assert public_directory_response.raw.read() == index_file_contents
|
163
|
+
|
164
|
+
def test_it_headers(self, public_directory_response: requests.Response):
|
165
|
+
assert public_directory_response.headers['Content-Type'] == 'text/html'
|
166
|
+
|
167
|
+
def test_it_sets_status_code(self, public_directory_response: requests.Response):
|
168
|
+
assert public_directory_response.status_code == 200
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import pdb
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
from click.testing import CliRunner
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from stoobly_agent.cli import mock, record, request
|
8
|
+
from stoobly_agent.lib.orm.request import Request
|
9
|
+
from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
|
10
|
+
|
11
|
+
@pytest.fixture(scope='module')
|
12
|
+
def runner():
|
13
|
+
return CliRunner()
|
14
|
+
|
15
|
+
class TestLifecycleHooks():
|
16
|
+
@pytest.fixture(scope='class', autouse=True)
|
17
|
+
def settings(self):
|
18
|
+
return reset()
|
19
|
+
|
20
|
+
@pytest.fixture(scope='class')
|
21
|
+
def lifecycle_hooks_path(self):
|
22
|
+
return str(Path(__file__).parent.parent / 'mock_data' / 'lifecycle_hooks.py')
|
23
|
+
|
24
|
+
@pytest.fixture(scope='class')
|
25
|
+
def record_result(self, runner: CliRunner, lifecycle_hooks_path: str):
|
26
|
+
record_result = runner.invoke(record, [
|
27
|
+
'--lifecycle-hooks-path', lifecycle_hooks_path, '--output', '/dev/null', DETERMINISTIC_GET_REQUEST_URL
|
28
|
+
])
|
29
|
+
assert record_result.exit_code == 0
|
30
|
+
return record_result
|
31
|
+
|
32
|
+
@pytest.fixture(scope='class')
|
33
|
+
def recorded_request(self):
|
34
|
+
return Request.last()
|
35
|
+
|
36
|
+
@pytest.fixture(scope='class')
|
37
|
+
def mock_result(self, runner: CliRunner, lifecycle_hooks_path: str):
|
38
|
+
mock_result = runner.invoke(mock, [
|
39
|
+
'--lifecycle-hooks-path', lifecycle_hooks_path, '--output', '/dev/null', DETERMINISTIC_GET_REQUEST_URL
|
40
|
+
])
|
41
|
+
assert mock_result.exit_code == 0
|
42
|
+
return mock_result
|
43
|
+
|
44
|
+
@pytest.fixture(scope='class')
|
45
|
+
def test_result(self, runner: CliRunner, lifecycle_hooks_path: str, recorded_request: Request):
|
46
|
+
test_result = runner.invoke(request, [
|
47
|
+
'test', '--format', 'json', '--lifecycle-hooks-path', lifecycle_hooks_path, recorded_request.key()
|
48
|
+
])
|
49
|
+
assert test_result.exit_code == 0
|
50
|
+
return test_result
|
51
|
+
|
52
|
+
def test_calls_record_hooks(self, record_result):
|
53
|
+
expected_stdout = ['before_request', 'before_record', 'after_record', 'before_response']
|
54
|
+
assert record_result.stdout.strip() == "\n".join(expected_stdout)
|
55
|
+
|
56
|
+
def test_calls_mock_hooks(self, mock_result):
|
57
|
+
expected_stdout = ['before_request', 'before_mock', 'after_mock', 'before_response']
|
58
|
+
assert mock_result.stdout.strip() == "\n".join(expected_stdout)
|
59
|
+
|
60
|
+
def test_calls_test_hooks(self, test_result):
|
61
|
+
expected_stdout = [
|
62
|
+
'before_request', 'before_replay', 'after_replay', 'before_mock', 'after_mock', 'before_test', 'after_test', 'before_response'
|
63
|
+
]
|
64
|
+
stdout = test_result.stdout
|
65
|
+
lifecycle_hooks_output = stdout.split('{')[0]
|
66
|
+
assert lifecycle_hooks_output.strip() == "\n".join(expected_stdout)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import pdb
|
2
|
+
import pytest
|
3
|
+
|
4
|
+
from click.testing import CliRunner
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, NON_DETERMINISTIC_GET_REQUEST_URL
|
8
|
+
|
9
|
+
from stoobly_agent.config.constants.custom_headers import SESSION_ID
|
10
|
+
from stoobly_agent.cli import mock, record, scenario
|
11
|
+
from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
|
12
|
+
from stoobly_agent.lib.orm.request import Request
|
13
|
+
|
14
|
+
@pytest.fixture(scope='module')
|
15
|
+
def runner():
|
16
|
+
return CliRunner()
|
17
|
+
|
18
|
+
class TestMocking():
|
19
|
+
@pytest.fixture(scope='class')
|
20
|
+
def lifecycle_hooks_path(self):
|
21
|
+
return str(Path(__file__).parent / 'mock_scenario_lifecycle_hooks.py')
|
22
|
+
|
23
|
+
class TestScenario():
|
24
|
+
@pytest.fixture(scope='class')
|
25
|
+
def scenario_key(self, runner: CliRunner):
|
26
|
+
res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
|
27
|
+
assert res.exit_code == 0
|
28
|
+
return ScenarioKey(res.stdout.strip()).raw
|
29
|
+
|
30
|
+
@pytest.fixture(autouse=True, scope='class')
|
31
|
+
def recorded_request1(self, runner: CliRunner, scenario_key: str):
|
32
|
+
record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
33
|
+
assert record_result.exit_code == 0
|
34
|
+
return Request.last()
|
35
|
+
|
36
|
+
@pytest.fixture(autouse=True, scope='class')
|
37
|
+
def recorded_request2(self, runner: CliRunner, scenario_key: str):
|
38
|
+
record_result = runner.invoke(record, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
39
|
+
assert record_result.exit_code == 0
|
40
|
+
return Request.last()
|
41
|
+
|
42
|
+
def test_it_does_not_mocks(self, runner: CliRunner, scenario_key: str):
|
43
|
+
mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, NON_DETERMINISTIC_GET_REQUEST_URL])
|
44
|
+
assert mock_result.exit_code == 1
|
45
|
+
|
46
|
+
def test_it_mocks(self, runner: CliRunner, scenario_key: str):
|
47
|
+
mock_result = runner.invoke(mock, ['--scenario-key', scenario_key, DETERMINISTIC_GET_REQUEST_URL])
|
48
|
+
assert mock_result.exit_code == 0
|
49
|
+
|
50
|
+
def test_it_mocks_in_order(
|
51
|
+
self, runner: CliRunner, lifecycle_hooks_path: str, recorded_request1: Request, recorded_request2: Request, scenario_key: str
|
52
|
+
):
|
53
|
+
session_id = 'test'
|
54
|
+
args = [
|
55
|
+
'--lifecycle-hooks-path', lifecycle_hooks_path, '--scenario-key', scenario_key, '--output', '/dev/null',
|
56
|
+
'-H', f"{SESSION_ID}: {session_id}", DETERMINISTIC_GET_REQUEST_URL
|
57
|
+
]
|
58
|
+
mock_result = runner.invoke(mock, args)
|
59
|
+
assert int(mock_result.stdout) == recorded_request1.id
|
60
|
+
|
61
|
+
mock_result = runner.invoke(mock, args)
|
62
|
+
assert int(mock_result.stdout) == recorded_request2.id
|
@@ -8,10 +8,10 @@ from stoobly_agent.test.test_helper import DETERMINISTIC_GET_REQUEST_URL, reset
|
|
8
8
|
|
9
9
|
from stoobly_agent.config.constants import mode
|
10
10
|
from stoobly_agent.app.settings.constants import request_component
|
11
|
-
from stoobly_agent.cli import config, mock, record
|
12
|
-
from stoobly_agent.lib.
|
11
|
+
from stoobly_agent.cli import config, mock, record
|
12
|
+
from stoobly_agent.lib.orm.request import Request
|
13
13
|
|
14
|
-
@pytest.fixture()
|
14
|
+
@pytest.fixture(scope='module')
|
15
15
|
def runner():
|
16
16
|
return CliRunner()
|
17
17
|
|
@@ -23,46 +23,60 @@ class TestMocking():
|
|
23
23
|
return reset()
|
24
24
|
|
25
25
|
class TestWhenHeaders():
|
26
|
+
header_name = 'foo'
|
27
|
+
header_value = 'bar'
|
28
|
+
url = DETERMINISTIC_GET_REQUEST_URL
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
url = DETERMINISTIC_GET_REQUEST_URL
|
32
|
-
record_result = runner.invoke(record, [url, '-H', f"{header_name}: {header_value}"])
|
30
|
+
@pytest.fixture(autouse=True, scope='class')
|
31
|
+
def recorded_request(self, runner: CliRunner):
|
32
|
+
record_result = runner.invoke(record, [self.url, '-H', f"{self.header_name}: {self.header_value}"])
|
33
33
|
assert record_result.exit_code == 0
|
34
|
+
return Request.last()
|
35
|
+
|
36
|
+
def test_it_ignores_headers_default(self, runner: CliRunner):
|
37
|
+
# Expect to fail since we have not set up rewrite rule for mock mode
|
38
|
+
mock_result = runner.invoke(mock, [self.url])
|
39
|
+
assert mock_result.exit_code == 0
|
34
40
|
|
41
|
+
def test_it_fails_when_enabled(self, runner: CliRunner):
|
35
42
|
# Enforce matching by headers only
|
36
43
|
match_result = runner.invoke(config, [
|
37
44
|
'match', 'set',
|
38
45
|
'--method', 'GET', '--mode', mode.MOCK, '--pattern', '.*?', '--component', request_component.HEADER
|
39
46
|
])
|
40
47
|
assert match_result.exit_code == 0
|
41
|
-
|
48
|
+
|
42
49
|
# Expect to fail since we have not set up rewrite rule for mock mode
|
43
|
-
mock_result = runner.invoke(mock, [url])
|
50
|
+
mock_result = runner.invoke(mock, [self.url])
|
44
51
|
assert mock_result.exit_code == 1
|
45
52
|
|
53
|
+
def test_it_succeeds_after_rewrite(self, runner: CliRunner):
|
46
54
|
rewrite_result = runner.invoke(config, [
|
47
55
|
'rewrite', 'set',
|
48
|
-
'--method', 'GET', '--mode', mode.MOCK, '--name', header_name, '--value', header_value, '--pattern', '.*?', '--type', request_component.HEADER
|
56
|
+
'--method', 'GET', '--mode', mode.MOCK, '--name', self.header_name, '--value', self.header_value, '--pattern', '.*?', '--type', request_component.HEADER
|
49
57
|
]
|
50
58
|
)
|
51
59
|
assert rewrite_result.exit_code == 0
|
52
60
|
|
53
|
-
mock_result = runner.invoke(mock, [url])
|
61
|
+
mock_result = runner.invoke(mock, [self.url])
|
54
62
|
assert mock_result.exit_code == 0
|
55
63
|
|
56
64
|
class TestWhenQueryParams():
|
65
|
+
query_param_name = 'foo'
|
66
|
+
query_param_value = 'bar'
|
67
|
+
url = DETERMINISTIC_GET_REQUEST_URL
|
57
68
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
url = DETERMINISTIC_GET_REQUEST_URL
|
63
|
-
record_result = runner.invoke(record, [f"{url}?{query_param_name}={query_param_value}"])
|
69
|
+
@pytest.fixture(autouse=True, scope='class')
|
70
|
+
def recorded_request(self, runner: CliRunner):
|
71
|
+
record_result = runner.invoke(record, [f"{self.url}?{self.query_param_name}={self.query_param_value}"])
|
64
72
|
assert record_result.exit_code == 0
|
73
|
+
return Request.last()
|
65
74
|
|
75
|
+
def test_it_ignores_query_params_by_default(self, runner: CliRunner):
|
76
|
+
mock_result = runner.invoke(mock, [self.url])
|
77
|
+
assert mock_result.exit_code == 0
|
78
|
+
|
79
|
+
def test_it_fails_when_enabled(self, runner: CliRunner):
|
66
80
|
# Enforce matching by query params only
|
67
81
|
match_result = runner.invoke(config, [
|
68
82
|
'match', 'set',
|
@@ -71,34 +85,42 @@ class TestMocking():
|
|
71
85
|
assert match_result.exit_code == 0
|
72
86
|
|
73
87
|
# Expect to fail since we have not set up rewrite rule for mock mode
|
74
|
-
mock_result = runner.invoke(mock, [url])
|
88
|
+
mock_result = runner.invoke(mock, [self.url])
|
75
89
|
assert mock_result.exit_code == 1
|
76
90
|
|
91
|
+
def test_it_succeeds_after_rewrites(self, runner: CliRunner):
|
77
92
|
rewrite_result = runner.invoke(config, [
|
78
93
|
'rewrite', 'set',
|
79
|
-
'--method', 'GET', '--mode', mode.MOCK, '--name', query_param_name, '--value', query_param_value, '--pattern', '.*?', '--type', request_component.QUERY_PARAM
|
94
|
+
'--method', 'GET', '--mode', mode.MOCK, '--name', self.query_param_name, '--value', self.query_param_value, '--pattern', '.*?', '--type', request_component.QUERY_PARAM
|
80
95
|
]
|
81
96
|
)
|
82
97
|
assert rewrite_result.exit_code == 0
|
83
98
|
|
84
|
-
mock_result = runner.invoke(mock, [url])
|
99
|
+
mock_result = runner.invoke(mock, [self.url])
|
85
100
|
assert mock_result.exit_code == 0
|
86
101
|
|
87
102
|
class TestWhenBodyParams():
|
103
|
+
body_param_name = 'foo'
|
104
|
+
body_param_value = 'bar'
|
105
|
+
url = DETERMINISTIC_GET_REQUEST_URL
|
88
106
|
|
89
|
-
|
90
|
-
|
91
|
-
body_param_value = 'bar'
|
92
|
-
|
107
|
+
@pytest.fixture(autouse=True, scope='class')
|
108
|
+
def recorded_request(self, runner: CliRunner):
|
93
109
|
content = {}
|
94
|
-
content[body_param_name] = body_param_value
|
110
|
+
content[self.body_param_name] = self.body_param_value
|
95
111
|
body = json.dumps(content)
|
96
112
|
|
97
113
|
url = DETERMINISTIC_GET_REQUEST_URL
|
98
114
|
record_result = runner.invoke(record, [url, '-d', body, '-H', 'Content-Type: application/json'])
|
99
115
|
assert record_result.exit_code == 0
|
116
|
+
return Request.last()
|
100
117
|
|
101
|
-
|
118
|
+
def test_it_ignores_body_params_by_default(self, runner: CliRunner):
|
119
|
+
mock_result = runner.invoke(mock, [self.url])
|
120
|
+
assert mock_result.exit_code == 0
|
121
|
+
|
122
|
+
def test_it_fails_when_enabled(self, runner: CliRunner):
|
123
|
+
# Enforce matching by bodyy params only
|
102
124
|
match_result = runner.invoke(config, [
|
103
125
|
'match', 'set',
|
104
126
|
'--method', 'GET', '--mode', mode.MOCK, '--pattern', '.*?', '--component', request_component.BODY_PARAM
|
@@ -106,23 +128,17 @@ class TestMocking():
|
|
106
128
|
assert match_result.exit_code == 0
|
107
129
|
|
108
130
|
# Expect to fail since we have not set up rewrite rule for mock mode
|
109
|
-
mock_result = runner.invoke(mock, [url])
|
131
|
+
mock_result = runner.invoke(mock, [self.url])
|
110
132
|
assert mock_result.exit_code == 1
|
111
133
|
|
134
|
+
def test_it_succeeds_after_rewrites(self, runner: CliRunner):
|
112
135
|
rewrite_result = runner.invoke(config, [
|
113
136
|
'rewrite', 'set',
|
114
|
-
'--method', 'GET', '--mode', mode.MOCK, '--name', body_param_name, '--value', body_param_value, '--pattern', '.*?', '--type', request_component.BODY_PARAM
|
137
|
+
'--method', 'GET', '--mode', mode.MOCK, '--name', self.body_param_name, '--value', self.body_param_value, '--pattern', '.*?', '--type', request_component.BODY_PARAM
|
115
138
|
]
|
116
139
|
)
|
117
140
|
assert rewrite_result.exit_code == 0
|
118
141
|
|
119
|
-
mock_result = runner.invoke(mock, [url])
|
142
|
+
mock_result = runner.invoke(mock, [self.url])
|
120
143
|
assert mock_result.exit_code == 0
|
121
144
|
|
122
|
-
class TestScenario():
|
123
|
-
|
124
|
-
@pytest.fixture
|
125
|
-
def scenario_key(self, runner: CliRunner):
|
126
|
-
res = runner.invoke(scenario, ['create', '--select', 'key', '--without-headers', 'test-scenario'])
|
127
|
-
assert res.exit_code == 0
|
128
|
-
return ScenarioKey(res.stdout.strip())
|