Repository: StackStorm/st2 (language: python)

Issue:
make mongo tests safer to run in parallel
### Background

I'm working towards introducing [`pants`](https://www.pantsbuild.org/docs). Eventually I would like to use pants to run tests to take advantage of the fine-grained per-file caching of results that accounts for dependencies by python files (pants infers the deps by reading the python files).

pants runs each test file separately in pytest. So, if two files try to modify the same database at the same time, we'll get strange results.


### Overview

So, modify the db name for each parallel test to ensure that they can run safely in parallel.

Once we integrate pants, we will configure (in pants.toml) `[pytest].execution_slot_var = "ST2TESTS_PARALLEL_SLOT"` to take advantage of the env var introduced here.
https://www.pantsbuild.org/docs/reference-pytest#section-execution-slot-var


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b746bbedb3..6564e6d62d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -43,6 +43,11 @@ Added
 * Added a `get_result` method to the `ExecutionResourceManager` Class for st2client
   Contributed by @skiedude
 
+* Added new env var for tests: `ST2TESTS_SYSTEM_USER`. When set, this will override `system_user.user` in st2 conf
+  so that you can run tests on systems that do not have the `stanley` user. When running tests locally, use the
+  following to set system user to the current user: `export ST2TESTS_SYSTEM_USER=$(id -un)` #6242
+  Contributed by @cognifloyd
+
 3.8.1 - December 13, 2023
 -------------------------
 Fixed
diff --git a/pants.toml b/pants.toml
index 41085db3a9..8cda497564 100644
--- a/pants.toml
+++ b/pants.toml
@@ -239,6 +239,13 @@ config = "@lint-configs/regex-lint.yaml"
 [setuptools]
 install_from_resolve = "st2"
 
+[test]
+extra_env_vars = [
+  # Use this so that the test system does not require the stanley user.
+  # For example: export ST2TESTS_SYSTEM_USER=${USER}
+  "ST2TESTS_SYSTEM_USER",
+]
+
 [twine]
 install_from_resolve = "twine"
 


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 3f2145828c..4ec93d2f56 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -93,6 +93,9 @@ jobs:
           gha-cache-key: cache0-py${{ matrix.python-version }}
 
       - name: Test
+        env:
+          # Github Actions uses the 'runner' user, so use that instead of stanley.
+          ST2TESTS_SYSTEM_USER: 'runner'
         # We do not support running pytest everywhere yet. When we do it will be simply:
         #   pants test ::
         # Until then, we need to manually adjust this command line to test what we can.
diff --git a/contrib/runners/orquesta_runner/tests/unit/test_basic.py b/contrib/runners/orquesta_runner/tests/unit/test_basic.py
index 7c9351423a..9b84c94b3a 100644
--- a/contrib/runners/orquesta_runner/tests/unit/test_basic.py
+++ b/contrib/runners/orquesta_runner/tests/unit/test_basic.py
@@ -21,6 +21,7 @@
 import six
 
 from orquesta import statuses as wf_statuses
+from oslo_config import cfg
 
 import st2tests
 
@@ -108,7 +109,7 @@ def get_runner_class(cls, runner_name):
         runners_utils, "invoke_post_run", mock.MagicMock(return_value=None)
     )
     def test_run_workflow(self):
-        username = "stanley"
+        username = cfg.CONF.system_user.user
         wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, "sequential.yaml")
         wf_input = {"who": "Thanos"}
         lv_ac_db = lv_db_models.LiveActionDB(
diff --git a/contrib/runners/orquesta_runner/tests/unit/test_context.py b/contrib/runners/orquesta_runner/tests/unit/test_context.py
index d9e726d9a1..983614a041 100644
--- a/contrib/runners/orquesta_runner/tests/unit/test_context.py
+++ b/contrib/runners/orquesta_runner/tests/unit/test_context.py
@@ -19,6 +19,7 @@
 import mock
 
 from orquesta import statuses as wf_statuses
+from oslo_config import cfg
 
 import st2tests
 
@@ -125,7 +126,7 @@ def test_runtime_context(self):
         expected_st2_ctx = {
             "action_execution_id": str(ac_ex_db.id),
             "api_url": "http://127.0.0.1/v1",
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "pack": "orquesta_tests",
             "action": "orquesta_tests.runtime-context",
             "runner": "orquesta",
@@ -208,9 +209,10 @@ def test_action_context_sys_user(self):
         self.assertEqual(wf_ex_db.status, wf_statuses.SUCCEEDED)
         self.assertEqual(lv_ac_db.status, ac_const.LIVEACTION_STATUS_SUCCEEDED)
 
+        user = cfg.CONF.system_user.user
         # Check result.
         expected_result = {
-            "output": {"msg": "stanley, All your base are belong to us!"}
+            "output": {"msg": f"{user}, All your base are belong to us!"}
         }
 
         self.assertDictEqual(lv_ac_db.result, expected_result)
diff --git a/contrib/runners/orquesta_runner/tests/unit/test_error_handling.py b/contrib/runners/orquesta_runner/tests/unit/test_error_handling.py
index 9a4dd1cd5b..9aafac018f 100644
--- a/contrib/runners/orquesta_runner/tests/unit/test_error_handling.py
+++ b/contrib/runners/orquesta_runner/tests/unit/test_error_handling.py
@@ -18,6 +18,7 @@
 import mock
 
 from orquesta import statuses as wf_statuses
+from oslo_config import cfg
 
 import st2tests
 
@@ -954,7 +955,7 @@ def test_fail_manually_with_recovery_failure(self):
         mock.MagicMock(side_effect=[RUNNER_RESULT_FAILED]),
     )
     def test_include_result_to_error_log(self):
-        username = "stanley"
+        username = cfg.CONF.system_user.user
         wf_meta = base.get_wf_fixture_meta_data(TEST_PACK_PATH, "sequential.yaml")
         wf_input = {"who": "Thanos"}
         lv_ac_db = lv_db_models.LiveActionDB(
diff --git a/st2actions/tests/unit/test_runner_container.py b/st2actions/tests/unit/test_runner_container.py
index 1134e176bf..a653403388 100644
--- a/st2actions/tests/unit/test_runner_container.py
+++ b/st2actions/tests/unit/test_runner_container.py
@@ -297,7 +297,10 @@ def test_dispatch(self):
         self.assertTrue(result.get("action_params").get("actionstr") == "bar")
 
         # Assert that context is written correctly.
-        context = {"user": "stanley", "third_party_system": {"ref_id": "1234"}}
+        context = {
+            "user": cfg.CONF.system_user.user,
+            "third_party_system": {"ref_id": "1234"},
+        }
 
         self.assertDictEqual(liveaction_db.context, context)
 
diff --git a/st2api/tests/unit/controllers/v1/test_alias_execution.py b/st2api/tests/unit/controllers/v1/test_alias_execution.py
index 44261fde3f..e30b754196 100644
--- a/st2api/tests/unit/controllers/v1/test_alias_execution.py
+++ b/st2api/tests/unit/controllers/v1/test_alias_execution.py
@@ -14,9 +14,10 @@
 # limitations under the License.
 
 import copy
-
 import mock
 
+from oslo_config import cfg
+
 from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
 from st2common.models.db.execution import ActionExecutionDB
 from st2common.services import action as action_service
@@ -241,7 +242,7 @@ def test_match_and_execute_matches_one(self, mock_request):
         self.assertIn("source_channel", mock_request.call_args[0][0].context.keys())
         self.assertEqual(actual_context["source_channel"], "chat-channel")
         self.assertEqual(actual_context["api_user"], "chat-user")
-        self.assertEqual(actual_context["user"], "stanley")
+        self.assertEqual(actual_context["user"], cfg.CONF.system_user.user)
 
     @mock.patch.object(action_service, "request", return_value=(None, EXECUTION))
     def test_match_and_execute_matches_one_multiple_match(self, mock_request):
@@ -398,7 +399,7 @@ def _do_post(
             "name": alias_execution.name,
             "format": format_str,
             "command": command,
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "source_channel": "test",
             "notification_route": "test",
         }
diff --git a/st2api/tests/unit/controllers/v1/test_auth.py b/st2api/tests/unit/controllers/v1/test_auth.py
index 695d490faa..4f7dd1961b 100644
--- a/st2api/tests/unit/controllers/v1/test_auth.py
+++ b/st2api/tests/unit/controllers/v1/test_auth.py
@@ -29,7 +29,7 @@
 from st2tests.fixturesloader import FixturesLoader
 
 OBJ_ID = bson.ObjectId()
-USER = "stanley"
+USER = cfg.CONF.system_user.user
 USER_DB = UserDB(name=USER)
 TOKEN = uuid.uuid4().hex
 NOW = date_utils.get_datetime_utc_now()
diff --git a/st2api/tests/unit/controllers/v1/test_executions.py b/st2api/tests/unit/controllers/v1/test_executions.py
index eb3face2ba..ea5e45b6fe 100644
--- a/st2api/tests/unit/controllers/v1/test_executions.py
+++ b/st2api/tests/unit/controllers/v1/test_executions.py
@@ -24,6 +24,8 @@
 from six.moves import filter
 from six.moves import http_client
 
+from oslo_config import cfg
+
 from st2common.constants import action as action_constants
 from st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE
 from st2common.constants.keyvalue import FULL_USER_SCOPE
@@ -686,7 +688,10 @@ def test_post_delete(self):
         delete_resp = self._do_delete(self._get_actionexecution_id(post_resp))
         self.assertEqual(delete_resp.status_int, 200)
         self.assertEqual(delete_resp.json["status"], "canceled")
-        expected_result = {"message": "Action canceled by user.", "user": "stanley"}
+        expected_result = {
+            "message": "Action canceled by user.",
+            "user": cfg.CONF.system_user.user,
+        }
         self.assertDictEqual(delete_resp.json["result"], expected_result)
 
     def test_post_delete_duplicate(self):
@@ -702,7 +707,10 @@ def test_post_delete_duplicate(self):
             delete_resp = self._do_delete(self._get_actionexecution_id(post_resp))
             self.assertEqual(delete_resp.status_int, 200)
             self.assertEqual(delete_resp.json["status"], "canceled")
-            expected_result = {"message": "Action canceled by user.", "user": "stanley"}
+            expected_result = {
+                "message": "Action canceled by user.",
+                "user": cfg.CONF.system_user.user,
+            }
             self.assertDictEqual(delete_resp.json["result"], expected_result)
 
     def test_post_delete_trace(self):
@@ -976,7 +984,7 @@ def test_template_encrypted_params(self):
                 ),
             },
             {
-                "name": "stanley:secret",
+                "name": f"{cfg.CONF.system_user.user}:secret",
                 "secret": True,
                 "scope": FULL_USER_SCOPE,
                 "value": crypto_utils.symmetric_encrypt(
@@ -994,18 +1002,18 @@ def test_template_encrypted_params(self):
         ]
         kvps = [KeyValuePair.add_or_update(KeyValuePairDB(**x)) for x in register_items]
 
-        # By default, encrypt_user_param will be read from stanley's scope
+        # By default, encrypt_user_param will be read from system_user's scope
         # 1. parameters are not marked as secret
         resp = self._do_post(LIVE_ACTION_DEFAULT_ENCRYPT)
         self.assertEqual(resp.status_int, 201)
-        self.assertEqual(resp.json["context"]["user"], "stanley")
+        self.assertEqual(resp.json["context"]["user"], cfg.CONF.system_user.user)
         self.assertEqual(resp.json["parameters"]["encrypted_param"], "foo")
         self.assertEqual(resp.json["parameters"]["encrypted_user_param"], "bar")
 
         # 2. parameters are marked as secret
         resp = self._do_post(LIVE_ACTION_DEFAULT_ENCRYPT_SECRET_PARAM)
         self.assertEqual(resp.status_int, 201)
-        self.assertEqual(resp.json["context"]["user"], "stanley")
+        self.assertEqual(resp.json["context"]["user"], cfg.CONF.system_user.user)
         self.assertEqual(
             resp.json["parameters"]["encrypted_param"], MASKED_ATTRIBUTE_VALUE
         )
@@ -1077,7 +1085,7 @@ def test_re_run_workflow_success(self):
         )
 
         expected_context = {
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "pack": "starterpack",
             "re-run": {"ref": execution_id},
             "trace_context": {"id_": str(trace.id)},
@@ -1106,7 +1114,7 @@ def test_re_run_workflow_task_success(self):
 
         expected_context = {
             "pack": "starterpack",
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "re-run": {"ref": execution_id, "tasks": data["tasks"]},
             "trace_context": {"id_": str(trace.id)},
         }
@@ -1134,7 +1142,7 @@ def test_re_run_workflow_tasks_success(self):
 
         expected_context = {
             "pack": "starterpack",
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "re-run": {"ref": execution_id, "tasks": data["tasks"]},
             "trace_context": {"id_": str(trace.id)},
         }
@@ -1162,7 +1170,7 @@ def test_re_run_workflow_tasks_reset_success(self):
 
         expected_context = {
             "pack": "starterpack",
-            "user": "stanley",
+            "user": cfg.CONF.system_user.user,
             "re-run": {
                 "ref": execution_id,
                 "tasks": data["tasks"],
diff --git a/st2tests/st2tests/config.py b/st2tests/st2tests/config.py
index 351456a261..bef5197bcf 100644
--- a/st2tests/st2tests/config.py
+++ b/st2tests/st2tests/config.py
@@ -104,6 +104,9 @@ def _override_common_opts():
     CONF.set_override(name="api_url", override="http://127.0.0.1", group="auth")
     CONF.set_override(name="mask_secrets", override=True, group="log")
     CONF.set_override(name="stream_output", override=False, group="actionrunner")
+    system_user = os.environ.get("ST2TESTS_SYSTEM_USER", "")
+    if system_user:
+        CONF.set_override(name="user", override=system_user, group="system_user")
 
 
 def _override_api_opts():