Repository: abhinavsingh/proxy.py
Pull request #1038 (2022-01-22 15:18:45)

[CacheByContentTypePlugin] Prepare for content type parsing
- Move default cache directory within `proxy.py` instance data directory


NOTE: `CacheByContentTypePlugin` plugin depends upon `CacheResponsesPlugin` which must be placed before it in the chain.


codecov[bot]: # [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) Report
> Merging [#1038](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (903b76c) into [develop](https://codecov.io/gh/abhinavsingh/proxy.py/commit/25e6cb0fe5703b66a9475c6bbd320292bf70765a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (25e6cb0) will **decrease** coverage by `0.36%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/graphs/tree.svg?width=650&height=150&src=pr&token=Zh9J7b4la2&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)

```diff
@@             Coverage Diff             @@
##           develop    #1038      +/-   ##
===========================================
- Coverage    86.97%   86.61%   -0.37%     
===========================================
  Files          150      150              
  Lines         6481     6478       -3     
  Branches       661      611      -50     
===========================================
- Hits          5637     5611      -26     
- Misses         734      742       +8     
- Partials       110      125      +15     
```

| Flag | Coverage Δ | |
|---|---|---|
| pytest | `86.61% <100.00%> (-0.18%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | Coverage Δ | |
|---|---|---|
| [proxy/common/constants.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29tbW9uL2NvbnN0YW50cy5weQ==) | `100.00% <100.00%> (ø)` | |
| [proxy/common/flag.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29tbW9uL2ZsYWcucHk=) | `91.48% <100.00%> (-1.32%)` | :arrow_down: |
| [proxy/plugin/cache/cache\_responses.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL2NhY2hlL2NhY2hlX3Jlc3BvbnNlcy5weQ==) | `100.00% <100.00%> (ø)` | |
| [proxy/plugin/cache/store/disk.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvcGx1Z2luL2NhY2hlL3N0b3JlL2Rpc2sucHk=) | `83.33% <100.00%> (-10.00%)` | :arrow_down: |
| [tests/integration/test\_integration.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-dGVzdHMvaW50ZWdyYXRpb24vdGVzdF9pbnRlZ3JhdGlvbi5weQ==) | `94.31% <0.00%> (-5.69%)` | :arrow_down: |
| [tests/test\_set\_open\_file\_limit.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-dGVzdHMvdGVzdF9zZXRfb3Blbl9maWxlX2xpbWl0LnB5) | `96.42% <0.00%> (-3.58%)` | :arrow_down: |
| [proxy/testing/test\_case.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvdGVzdGluZy90ZXN0X2Nhc2UucHk=) | `97.77% <0.00%> (-2.23%)` | :arrow_down: |
| [proxy/core/work/threadless.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL3RocmVhZGxlc3MucHk=) | `80.56% <0.00%> (-1.98%)` | :arrow_down: |
| [proxy/http/handler.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvaHR0cC9oYW5kbGVyLnB5) | `74.43% <0.00%> (-1.90%)` | :arrow_down: |
| ... and [7 more](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Last update [25e6cb0...903b76c](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1038?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).


Patch:
diff --git a/helper/benchmark.sh b/helper/benchmark.sh
deleted file mode 100755
index b03651d..0000000
--- a/helper/benchmark.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/bash
-#
-# proxy.py
-# ~~~~~~~~
-# ⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable
-#     proxy server for Application debugging, testing and development.
-#
-# :copyright: (c) 2013-present by Abhinav Singh and contributors.
-# :license: BSD, see LICENSE for more details.
-#
-usage() {
-  echo "Usage: ./helper/benchmark.sh"
-  echo "You must run this script from proxy.py repo root."
-}
-
-DIRNAME=$(dirname "$0")
-if [ "$DIRNAME" != "./helper" ]; then
-  usage
-  exit 1
-fi
-
-BASENAME=$(basename "$0")
-if [ "$BASENAME" != "benchmark.sh" ]; then
-  usage
-  exit 1
-fi
-
-PWD=$(pwd)
-if [ $(basename $PWD) != "proxy.py" ]; then
-  usage
-  exit 1
-fi
-
-TIMEOUT=1sec
-CONCURRENCY=100
-TOTAL_REQUESTS=100000
-OPEN_FILE_LIMIT=65536
-BACKLOG=OPEN_FILE_LIMIT
-PID_FILE=/tmp/proxy.pid
-
-ulimit -n $OPEN_FILE_LIMIT
-
-PID=$(cat $PID_FILE)
-if [[ -z "$PID" ]]; then
-  echo "Either pid file doesn't exist or no pid found in the pid file"
-  exit 1
-fi
-ADDR=$(lsof -Pan -p $PID -i | grep -v COMMAND | awk '{ print $9 }')
-
-PRE_RUN_OPEN_FILES=$(./helper/monitor_open_files.sh)
-
-run_benchmark() {
-  echo "CONCURRENCY: $CONCURRENCY workers, TOTAL REQUESTS: $TOTAL_REQUESTS req"
-  oha \
-    --no-tui \
-    --latency-correction \
-    -n $TOTAL_REQUESTS \
-    -c $CONCURRENCY \
-    -t $TIMEOUT \
-    http://$ADDR/http-route-example
-}
-
-run_benchmark
-
-POST_RUN_OPEN_FILES=$(./helper/monitor_open_files.sh)
-
-echo "Open files diff:"
-diff <( echo "$PRE_RUN_OPEN_FILES" ) <( echo "$POST_RUN_OPEN_FILES" )
-
-# while true; do netstat -ant | grep .8899 | awk '{print $6}' | sort | uniq -c | sort -n; sleep 1; done
diff --git a/proxy/common/constants.py b/proxy/common/constants.py
index 3f58c51..3ec1acb 100644
--- a/proxy/common/constants.py
+++ b/proxy/common/constants.py
@@ -145,6 +145,9 @@ DEFAULT_DEVTOOLS_FRAME_ID = secrets.token_hex(8)
 DEFAULT_DEVTOOLS_LOADER_ID = secrets.token_hex(8)
 
 DEFAULT_DATA_DIRECTORY_PATH = os.path.join(str(pathlib.Path.home()), '.proxy')
+DEFAULT_CACHE_DIRECTORY_PATH = os.path.join(
+    DEFAULT_DATA_DIRECTORY_PATH, 'cache',
+)
 
 # Cor plugins enabled by default or via flags
 DEFAULT_ABC_PLUGINS = [
diff --git a/proxy/common/flag.py b/proxy/common/flag.py
index 279752f..35849d2 100644
--- a/proxy/common/flag.py
+++ b/proxy/common/flag.py
@@ -395,6 +395,11 @@ class FlagParser:
             )
         os.makedirs(args.ca_cert_dir, exist_ok=True)
 
+        # FIXME: Necessary here until flags framework provides a way
+        # for flag owners to initialize
+        os.makedirs(args.cache_dir, exist_ok=True)
+        os.makedirs(os.path.join(args.cache_dir, 'response'), exist_ok=True)
+
         return args
 
     @staticmethod
diff --git a/proxy/common/utils.py b/proxy/common/utils.py
index 9dc11ef..2141e2c 100644
--- a/proxy/common/utils.py
+++ b/proxy/common/utils.py
@@ -16,6 +16,7 @@ import ssl
 import sys
 import socket
 import logging
+import argparse
 import functools
 import ipaddress
 import contextlib
@@ -34,6 +35,13 @@ if not IS_WINDOWS:  # pragma: no cover
 logger = logging.getLogger(__name__)
 
 
+def tls_interception_enabled(flags: argparse.Namespace) -> bool:
+    return flags.ca_key_file is not None and \
+        flags.ca_cert_dir is not None and \
+        flags.ca_signing_key_file is not None and \
+        flags.ca_cert_file is not None
+
+
 def is_threadless(threadless: bool, threaded: bool) -> bool:
     # if default is threadless then return true unless
     # user has overridden mode using threaded flag.
diff --git a/proxy/http/descriptors.py b/proxy/http/descriptors.py
index 102629b..ef73496 100644
--- a/proxy/http/descriptors.py
+++ b/proxy/http/descriptors.py
@@ -8,8 +8,6 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
-from typing import Any
-
 from ..common.types import Readables, Writables, Descriptors
 
 
@@ -19,10 +17,6 @@ class DescriptorsHandlerMixin:
     include web and proxy plugins.  By using DescriptorsHandlerMixin, class
     becomes complaint with core event loop."""
 
-    def __init__(self, *args: Any, **kwargs: Any) -> None:
-        # FIXME: Required for multi-level inheritance to work
-        super().__init__(*args, **kwargs)   # type: ignore
-
     # @abstractmethod
     async def get_descriptors(self) -> Descriptors:
         """Implementations must return a list of descriptions that they wish to
diff --git a/proxy/http/mixins.py b/proxy/http/mixins.py
deleted file mode 100644
index 073c687..0000000
--- a/proxy/http/mixins.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    proxy.py
-    ~~~~~~~~
-    ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
-    Network monitoring, controls & Application development, testing, debugging.
-
-    :copyright: (c) 2013-present by Abhinav Singh and contributors.
-    :license: BSD, see LICENSE for more details.
-"""
-import argparse
-from typing import Any
-
-
-class TlsInterceptionPropertyMixin:
-    """A mixin which provides `tls_interception_enabled` property.
-
-    This is mostly for use by core & external developer HTTP plugins.
-    """
-
-    def __init__(self, *args: Any, **kwargs: Any) -> None:
-        # super().__init__(*args, **kwargs)
-        self.flags: argparse.Namespace = args[1]
-
-    @property
-    def tls_interception_enabled(self) -> bool:
-        return self.flags.ca_key_file is not None and \
-            self.flags.ca_cert_dir is not None and \
-            self.flags.ca_signing_key_file is not None and \
-            self.flags.ca_cert_file is not None
diff --git a/proxy/http/plugin.py b/proxy/http/plugin.py
index 9eaa097..ff37173 100644
--- a/proxy/http/plugin.py
+++ b/proxy/http/plugin.py
@@ -13,11 +13,11 @@ import argparse
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, List, Union, Optional
 
-from .mixins import TlsInterceptionPropertyMixin
 from .parser import HttpParser
 from .connection import HttpClientConnection
 from ..core.event import EventQueue
 from .descriptors import DescriptorsHandlerMixin
+from ..common.utils import tls_interception_enabled
 
 
 if TYPE_CHECKING:   # pragma: no cover
@@ -26,7 +26,6 @@ if TYPE_CHECKING:   # pragma: no cover
 
 class HttpProtocolHandlerPlugin(
         DescriptorsHandlerMixin,
-        TlsInterceptionPropertyMixin,
         ABC,
 ):
     """Base HttpProtocolHandler Plugin class.
@@ -59,7 +58,6 @@ class HttpProtocolHandlerPlugin(
             event_queue: Optional[EventQueue] = None,
             upstream_conn_pool: Optional['UpstreamConnectionPool'] = None,
     ):
-        super().__init__(uid, flags, client, event_queue, upstream_conn_pool)
         self.uid: str = uid
         self.flags: argparse.Namespace = flags
         self.client: HttpClientConnection = client
@@ -95,3 +93,7 @@ class HttpProtocolHandlerPlugin(
         perform any cleanup work here.
         """
         pass  # pragma: no cover
+
+    @property
+    def tls_interception_enabled(self) -> bool:
+        return tls_interception_enabled(self.flags)
diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py
index ed951e3..0fe1028 100644
--- a/proxy/http/proxy/plugin.py
+++ b/proxy/http/proxy/plugin.py
@@ -12,11 +12,11 @@ import argparse
 from abc import ABC
 from typing import TYPE_CHECKING, Any, Dict, Tuple, Optional
 
-from ..mixins import TlsInterceptionPropertyMixin
 from ..parser import HttpParser
 from ..connection import HttpClientConnection
 from ...core.event import EventQueue
 from ..descriptors import DescriptorsHandlerMixin
+from ...common.utils import tls_interception_enabled
 
 
 if TYPE_CHECKING:   # pragma: no cover
@@ -25,7 +25,6 @@ if TYPE_CHECKING:   # pragma: no cover
 
 class HttpProxyBasePlugin(
         DescriptorsHandlerMixin,
-        TlsInterceptionPropertyMixin,
         ABC,
 ):
     """Base HttpProxyPlugin Plugin class.
@@ -40,7 +39,6 @@ class HttpProxyBasePlugin(
             event_queue: EventQueue,
             upstream_conn_pool: Optional['UpstreamConnectionPool'] = None,
     ) -> None:
-        super().__init__(uid, flags, client, event_queue, upstream_conn_pool)
         self.uid = uid                  # pragma: no cover
         self.flags = flags              # pragma: no cover
         self.client = client            # pragma: no cover
@@ -170,4 +168,4 @@ class HttpProxyBasePlugin(
         flags BUT only conditionally enable interception for
         certain requests.
         """
-        return self.tls_interception_enabled
+        return tls_interception_enabled(self.flags)
diff --git a/proxy/plugin/__init__.py b/proxy/plugin/__init__.py
index e76a253..5bfb922 100644
--- a/proxy/plugin/__init__.py
+++ b/proxy/plugin/__init__.py
@@ -26,12 +26,14 @@ from .filter_by_upstream import FilterByUpstreamHostPlugin
 from .custom_dns_resolver import CustomDnsResolverPlugin
 from .filter_by_client_ip import FilterByClientIpPlugin
 from .filter_by_url_regex import FilterByURLRegexPlugin
+from .cache_by_content_type import CacheByContentTypePlugin
 from .modify_chunk_response import ModifyChunkResponsePlugin
 from .redirect_to_custom_server import RedirectToCustomServerPlugin
 
 
 __all__ = [
     'CacheResponsesPlugin',
+    'CacheByContentTypePlugin',
     'BaseCacheResponsesPlugin',
     'FilterByUpstreamHostPlugin',
     'ManInTheMiddlePlugin',
diff --git a/proxy/plugin/cache/cache_responses.py b/proxy/plugin/cache/cache_responses.py
index e9ccd12..cbc57eb 100644
--- a/proxy/plugin/cache/cache_responses.py
+++ b/proxy/plugin/cache/cache_responses.py
@@ -8,8 +8,9 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
+import os
 import multiprocessing
-from typing import Any
+from typing import Any, Dict, Optional
 
 from .base import BaseCacheResponsesPlugin
 from .store.disk import OnDiskCacheStore
@@ -24,6 +25,16 @@ class CacheResponsesPlugin(BaseCacheResponsesPlugin):
     def __init__(self, *args: Any, **kwargs: Any) -> None:
         super().__init__(*args, **kwargs)
         self.disk_store = OnDiskCacheStore(
-            uid=self.uid, cache_dir=self.flags.cache_dir,
+            uid=self.uid,
+            cache_dir=os.path.join(
+                self.flags.cache_dir,
+                'responses',
+            ),
         )
         self.set_store(self.disk_store)
+
+    def on_access_log(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+        context.update({
+            'cache_file_path': self.disk_store.cache_file_path,
+        })
+        return super().on_access_log(context)
diff --git a/proxy/plugin/cache/store/disk.py b/proxy/plugin/cache/store/disk.py
index 8ea54b8..a9ac027 100644
--- a/proxy/plugin/cache/store/disk.py
+++ b/proxy/plugin/cache/store/disk.py
@@ -10,13 +10,13 @@
 """
 import os
 import logging
-import tempfile
 from typing import BinaryIO, Optional
 
 from .base import CacheStore
 from ....common.flag import flags
 from ....http.parser import HttpParser
 from ....common.utils import text_
+from ....common.constants import DEFAULT_CACHE_DIRECTORY_PATH
 
 
 logger = logging.getLogger(__name__)
@@ -25,8 +25,8 @@ logger = logging.getLogger(__name__)
 flags.add_argument(
     '--cache-dir',
     type=str,
-    default=tempfile.gettempdir(),
-    help='Default: A temporary directory.  ' +
+    default=DEFAULT_CACHE_DIRECTORY_PATH,
+    help='Default: ' + DEFAULT_CACHE_DIRECTORY_PATH + '.  ' +
     'Flag only applicable when cache plugin is used with on-disk storage.',
 )
 
diff --git a/proxy/plugin/cache_by_content_type.py b/proxy/plugin/cache_by_content_type.py
new file mode 100644
index 0000000..a51f450
--- /dev/null
+++ b/proxy/plugin/cache_by_content_type.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+"""
+    proxy.py
+    ~~~~~~~~
+    ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
+    Network monitoring, controls & Application development, testing, debugging.
+
+    :copyright: (c) 2013-present by Abhinav Singh and contributors.
+    :license: BSD, see LICENSE for more details.
+"""
+import logging
+from typing import Any, Dict, Optional
+
+from ..http.proxy import HttpProxyBasePlugin
+from ..common.utils import tls_interception_enabled
+
+
+logger = logging.getLogger(__name__)
+
+
+class CacheByContentTypePlugin(HttpProxyBasePlugin):
+    """This plugin is supposed to work with
+    :py:class:`~proxy.plugin.cache.cache_responses.CacheResponsesPlugin`.  This plugin
+    must be put after the cache response plugin in the chain.
+
+    Plugin will try to extract out content type from the responses.
+    When found, data is stored under ``proxy.py`` instance data directory."""
+
+    def on_access_log(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+        if tls_interception_enabled(self.flags) and 'cache_file_path' in context:
+            print('cache file found')
+        return super().on_access_log(context)


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index d0bc8c4..ce341c8 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -30,8 +30,9 @@ from proxy.common.constants import (  # noqa: WPS450
     DEFAULT_ENABLE_SSH_TUNNEL, DEFAULT_ENABLE_WEB_SERVER,
     DEFAULT_DISABLE_HTTP_PROXY, PLUGIN_WEBSOCKET_TRANSPORT,
     DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_CLIENT_RECVBUF_SIZE,
-    DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_ENABLE_REVERSE_PROXY,
-    DEFAULT_ENABLE_STATIC_SERVER, _env_threadless_compliant,
+    DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_CACHE_DIRECTORY_PATH,
+    DEFAULT_ENABLE_REVERSE_PROXY, DEFAULT_ENABLE_STATIC_SERVER,
+    _env_threadless_compliant,
 )
 
 
@@ -79,6 +80,7 @@ class TestMain(unittest.TestCase):
         mock_args.enable_ssh_tunnel = DEFAULT_ENABLE_SSH_TUNNEL
         mock_args.enable_reverse_proxy = DEFAULT_ENABLE_REVERSE_PROXY
         mock_args.unix_socket_path = None
+        mock_args.cache_dir = DEFAULT_CACHE_DIRECTORY_PATH
 
     @mock.patch('os.remove')
     @mock.patch('os.path.exists')