mainsequence 9.0.6.dev41__tar.gz → 9.0.7.dev42__tar.gz
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.
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/PKG-INFO +1 -1
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/SOURCES.txt +0 -2
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/pyproject.toml +1 -1
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_auth_precedence.py +10 -52
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_cli_auth_contract.py +2 -61
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_cli_secure_storage.py +1 -8
- mainsequence-9.0.7.dev42/tests/test_client.py +50 -0
- mainsequence-9.0.7.dev42/tests/test_code_repository_documentation_contract.py +110 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_dependency_extras.py +0 -39
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_http_retry_budget.py +9 -22
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_job_client.py +2 -27
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_logconf.py +8 -50
- mainsequence-9.0.6.dev41/tests/test_cli.py +0 -8527
- mainsequence-9.0.6.dev41/tests/test_client.py +0 -70
- mainsequence-9.0.6.dev41/tests/test_code_repository_documentation_contract.py +0 -222
- mainsequence-9.0.6.dev41/tests/test_filter_normalization.py +0 -4433
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/LICENSE +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/README.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/AGENTS.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/mainsequence-inference/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/__init__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/__main__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/_request_identity.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/bootstrap.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/api.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/cli.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/code_repository_status.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/config.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/cli/ui.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/__init__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/_transport.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/base.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/client.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/github_issues.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/inference.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/models_user.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/observability.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/client/value_sets.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/code_repository_context.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/code_repository_skills.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/defaults.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/logconf.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/repository_identity_security.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/scaffold_skills.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/server/__init__.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/server/caller_assertions.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence/server/fastapi.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/setup.cfg +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_auth_bootstrap.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_caller_assertions.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_code_repository_branch_contract.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_code_repository_runtime_context.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_code_repository_skills.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_dev_release_version.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_environment_response_contracts.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_fastapi_request_identity.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_github_issues_client.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_independent_context.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_inference_client.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_instrumentation.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_local_ops.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_observability_client.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_scaffold_skills.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_secret_client_model.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_source_ownership.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_ssh_utils.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_tolerant_response_reading.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_tolerant_value_sets.py +0 -0
- {mainsequence-9.0.6.dev41 → mainsequence-9.0.7.dev42}/tests/test_user_application_facts.py +0 -0
|
@@ -66,7 +66,6 @@ tests/test_auth_bootstrap.py
|
|
|
66
66
|
tests/test_auth_precedence.py
|
|
67
67
|
tests/test_caller_assertions.py
|
|
68
68
|
tests/test_canonical_api_route_contract.py
|
|
69
|
-
tests/test_cli.py
|
|
70
69
|
tests/test_cli_auth_contract.py
|
|
71
70
|
tests/test_cli_browser_auth.py
|
|
72
71
|
tests/test_cli_secure_storage.py
|
|
@@ -80,7 +79,6 @@ tests/test_dev_release_version.py
|
|
|
80
79
|
tests/test_environment_resource_context.py
|
|
81
80
|
tests/test_environment_response_contracts.py
|
|
82
81
|
tests/test_fastapi_request_identity.py
|
|
83
|
-
tests/test_filter_normalization.py
|
|
84
82
|
tests/test_github_issues_client.py
|
|
85
83
|
tests/test_http_retry_budget.py
|
|
86
84
|
tests/test_independent_context.py
|
|
@@ -4,68 +4,23 @@ import base64
|
|
|
4
4
|
import importlib
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import sys
|
|
9
7
|
import time
|
|
10
|
-
import types
|
|
11
8
|
|
|
12
9
|
import pytest
|
|
13
10
|
import requests
|
|
14
11
|
|
|
12
|
+
from tests._support import load_sdk_submodule as _load_mainsequence_submodule
|
|
13
|
+
|
|
14
|
+
pytestmark = pytest.mark.usefixtures("isolated_sdk_imports", "clean_runtime_context_environment")
|
|
15
|
+
|
|
16
|
+
|
|
15
17
|
_REMOVED_DOMAIN_TOKEN = "PRO" + "JECT"
|
|
16
18
|
_UNSUPPORTED_REPOSITORY_UID_ENV = "MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_UID"
|
|
17
|
-
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV =
|
|
18
|
-
"MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_BRANCH_UID"
|
|
19
|
-
)
|
|
19
|
+
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV = "MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_BRANCH_UID"
|
|
20
20
|
_UNSUPPORTED_ENVIRONMENT_UID_ENV = (
|
|
21
21
|
"MAIN_SEQUENCE_ORGANIZATION_" + _REMOVED_DOMAIN_TOKEN + "_ENVIRONMENT_UID"
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
_RUNTIME_CONTEXT_ENV_NAMES = (
|
|
25
|
-
_UNSUPPORTED_REPOSITORY_UID_ENV,
|
|
26
|
-
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV,
|
|
27
|
-
"MAINSEQUENCE_REPOSITORY_BRANCH",
|
|
28
|
-
_UNSUPPORTED_ENVIRONMENT_UID_ENV,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@pytest.fixture(autouse=True)
|
|
33
|
-
def _clear_runtime_context_environment():
|
|
34
|
-
original_modules = {
|
|
35
|
-
name: module
|
|
36
|
-
for name, module in sys.modules.items()
|
|
37
|
-
if name == "mainsequence" or name.startswith("mainsequence.")
|
|
38
|
-
}
|
|
39
|
-
for name in _RUNTIME_CONTEXT_ENV_NAMES:
|
|
40
|
-
os.environ.pop(name, None)
|
|
41
|
-
yield
|
|
42
|
-
for name in _RUNTIME_CONTEXT_ENV_NAMES:
|
|
43
|
-
os.environ.pop(name, None)
|
|
44
|
-
for name in tuple(sys.modules):
|
|
45
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
46
|
-
sys.modules.pop(name, None)
|
|
47
|
-
sys.modules.update(original_modules)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _load_mainsequence_submodule(module_name: str):
|
|
51
|
-
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
|
52
|
-
pkg_root = repo_root / "mainsequence"
|
|
53
|
-
client_pkg_root = pkg_root / "client"
|
|
54
|
-
|
|
55
|
-
for name in tuple(sys.modules):
|
|
56
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
57
|
-
sys.modules.pop(name, None)
|
|
58
|
-
|
|
59
|
-
pkg = types.ModuleType("mainsequence")
|
|
60
|
-
pkg.__path__ = [str(pkg_root)]
|
|
61
|
-
sys.modules["mainsequence"] = pkg
|
|
62
|
-
|
|
63
|
-
client_pkg = types.ModuleType("mainsequence.client")
|
|
64
|
-
client_pkg.__path__ = [str(client_pkg_root)]
|
|
65
|
-
sys.modules["mainsequence.client"] = client_pkg
|
|
66
|
-
|
|
67
|
-
return importlib.import_module(module_name)
|
|
68
|
-
|
|
69
24
|
|
|
70
25
|
class _FakeResponse:
|
|
71
26
|
def __init__(self, status_code: int, payload: dict | None = None):
|
|
@@ -363,7 +318,10 @@ def test_runtime_credential_provider_does_not_mutate_unsupported_identity_enviro
|
|
|
363
318
|
assert first_headers["Authorization"] == "Bearer organization-runtime-access"
|
|
364
319
|
assert second_headers == first_headers
|
|
365
320
|
assert calls["count"] == 1
|
|
366
|
-
assert
|
|
321
|
+
assert (
|
|
322
|
+
os.environ[_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV]
|
|
323
|
+
== "caller-supplied-value-must-be-ignored"
|
|
324
|
+
)
|
|
367
325
|
|
|
368
326
|
|
|
369
327
|
def test_runtime_credential_provider_force_refresh_exchanges_again(monkeypatch):
|
|
@@ -9,6 +9,7 @@ import requests
|
|
|
9
9
|
from typer.testing import CliRunner
|
|
10
10
|
|
|
11
11
|
from mainsequence.cli import cli as cli_mod
|
|
12
|
+
from tests._support import jwt_with_expiry as _jwt
|
|
12
13
|
|
|
13
14
|
runner = CliRunner()
|
|
14
15
|
|
|
@@ -32,59 +33,6 @@ def test_cli_exposes_core_and_code_repository_commands():
|
|
|
32
33
|
assert "code-repository" in result.output
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def test_manual_jwt_login_persists_tokens(monkeypatch):
|
|
36
|
-
saved = {}
|
|
37
|
-
monkeypatch.setattr(cli_mod.cfg, "backend_url", lambda: "https://backend.example")
|
|
38
|
-
monkeypatch.setattr(
|
|
39
|
-
cli_mod.cfg,
|
|
40
|
-
"save_tokens",
|
|
41
|
-
lambda username, access, refresh: (
|
|
42
|
-
saved.update(username=username, access=access, refresh=refresh) or True
|
|
43
|
-
),
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
result = runner.invoke(
|
|
47
|
-
cli_mod.app, ["login", "--access-token", "access", "--refresh-token", "refresh"]
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
assert result.exit_code == 0
|
|
51
|
-
assert saved == {"username": "", "access": "access", "refresh": "refresh"}
|
|
52
|
-
assert "Signed in" in result.output
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_browser_login_uses_existing_auth_helper(monkeypatch):
|
|
56
|
-
monkeypatch.setattr(cli_mod.cfg, "backend_url", lambda: "https://backend.example")
|
|
57
|
-
monkeypatch.setattr(cli_mod.cfg, "save_tokens", lambda *_: True)
|
|
58
|
-
monkeypatch.setattr(
|
|
59
|
-
cli_mod,
|
|
60
|
-
"login_via_browser",
|
|
61
|
-
lambda **kwargs: {"access": "browser-access", "refresh": "browser-refresh"},
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
result = runner.invoke(cli_mod.app, ["login", "--no-open"])
|
|
65
|
-
|
|
66
|
-
assert result.exit_code == 0
|
|
67
|
-
assert "Signed in" in result.output
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def test_mcp_login_uses_existing_handoff_helper(monkeypatch):
|
|
71
|
-
monkeypatch.setattr(cli_mod.cfg, "backend_url", lambda: "https://backend.example")
|
|
72
|
-
monkeypatch.setattr(cli_mod.cfg, "save_tokens", lambda *_: True)
|
|
73
|
-
|
|
74
|
-
def handoff(**kwargs):
|
|
75
|
-
kwargs["on_handoff"](
|
|
76
|
-
{"mcp_tool": "auth.cli_authorize", "mcp_arguments": {"handoff_uid": "h"}}
|
|
77
|
-
)
|
|
78
|
-
return {"access": "mcp-access", "refresh": "mcp-refresh"}
|
|
79
|
-
|
|
80
|
-
monkeypatch.setattr(cli_mod, "login_via_mcp_handoff", handoff)
|
|
81
|
-
|
|
82
|
-
result = runner.invoke(cli_mod.app, ["login", "--mcp"])
|
|
83
|
-
|
|
84
|
-
assert result.exit_code == 0
|
|
85
|
-
assert "auth.cli_authorize" in result.output
|
|
86
|
-
|
|
87
|
-
|
|
88
36
|
def test_logout_revokes_before_clearing(monkeypatch):
|
|
89
37
|
calls = []
|
|
90
38
|
monkeypatch.setattr(
|
|
@@ -99,6 +47,7 @@ def test_logout_revokes_before_clearing(monkeypatch):
|
|
|
99
47
|
|
|
100
48
|
assert result.exit_code == 0
|
|
101
49
|
assert calls == ["revoke", "clear", "overrides"]
|
|
50
|
+
assert "backend session revoked" in result.output
|
|
102
51
|
|
|
103
52
|
|
|
104
53
|
def test_doctor_uses_full_local_diagnostics(monkeypatch):
|
|
@@ -396,14 +345,6 @@ def _now() -> int:
|
|
|
396
345
|
return int(time.time())
|
|
397
346
|
|
|
398
347
|
|
|
399
|
-
def _jwt(expiry: int | None) -> str:
|
|
400
|
-
import base64
|
|
401
|
-
|
|
402
|
-
claims = {} if expiry is None else {"exp": expiry}
|
|
403
|
-
body = base64.urlsafe_b64encode(json.dumps(claims).encode()).decode().rstrip("=")
|
|
404
|
-
return f"e30.{body}.signature"
|
|
405
|
-
|
|
406
|
-
|
|
407
348
|
def test_current_access_token_reuses_a_token_that_is_still_valid(monkeypatch):
|
|
408
349
|
from mainsequence.cli import api as api_mod
|
|
409
350
|
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
from keyring import errors as keyring_errors
|
|
8
8
|
|
|
9
9
|
from mainsequence.cli import config
|
|
10
|
+
from tests._support import jwt_with_expiry as _jwt
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class MemoryKeyring:
|
|
@@ -897,14 +898,6 @@ def test_macos_record_updated_by_a_released_version_is_still_read(monkeypatch, t
|
|
|
897
898
|
assert config.clear_tokens() is True
|
|
898
899
|
|
|
899
900
|
|
|
900
|
-
def _jwt(expiry: int | None) -> str:
|
|
901
|
-
import base64
|
|
902
|
-
|
|
903
|
-
claims = {} if expiry is None else {"exp": expiry}
|
|
904
|
-
body = base64.urlsafe_b64encode(json.dumps(claims).encode()).decode().rstrip("=")
|
|
905
|
-
return f"e30.{body}.signature"
|
|
906
|
-
|
|
907
|
-
|
|
908
901
|
def test_token_expiry_reads_the_claim_and_tolerates_other_values():
|
|
909
902
|
assert config.token_expiry(_jwt(1_900_000_000)) == 1_900_000_000
|
|
910
903
|
assert config.token_expiry(_jwt(None)) is None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import mainsequence.client as msc
|
|
4
|
+
|
|
5
|
+
# Every test in this module drives a real backend over the network and needs
|
|
6
|
+
# SDK credentials, so none of them can run in CI. See `[tool.pytest.ini_options]`
|
|
7
|
+
# in pyproject.toml: `pytest` deselects them, `pytest -m live` runs them.
|
|
8
|
+
pytestmark = pytest.mark.live
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_code_repository_image_filter():
|
|
12
|
+
images = msc.CodeRepositoryImage.filter()
|
|
13
|
+
if not images:
|
|
14
|
+
pytest.skip("No CodeRepository images available for filter test.")
|
|
15
|
+
|
|
16
|
+
image = images[0]
|
|
17
|
+
repo_hash = image.code_repository_commit_hash
|
|
18
|
+
|
|
19
|
+
filtered_by_hash = msc.CodeRepositoryImage.filter(code_repository_commit_hash=repo_hash)
|
|
20
|
+
assert any(img.uid == image.uid for img in filtered_by_hash)
|
|
21
|
+
|
|
22
|
+
filtered_by_hash_in = msc.CodeRepositoryImage.filter(
|
|
23
|
+
code_repository_commit_hash__in=[repo_hash]
|
|
24
|
+
)
|
|
25
|
+
assert any(img.uid == image.uid for img in filtered_by_hash_in)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_code_repository_resource_filter():
|
|
29
|
+
resources = msc.CodeRepositoryResource.filter()
|
|
30
|
+
if not resources:
|
|
31
|
+
pytest.skip("No CodeRepository resources available for filter test.")
|
|
32
|
+
|
|
33
|
+
resource = resources[0]
|
|
34
|
+
filtered_by_uid = msc.CodeRepositoryResource.filter(uid=resource.uid)
|
|
35
|
+
assert any(item.uid == resource.uid for item in filtered_by_uid)
|
|
36
|
+
|
|
37
|
+
filtered_by_uid_in = msc.CodeRepositoryResource.filter(uid__in=[resource.uid])
|
|
38
|
+
assert any(item.uid == resource.uid for item in filtered_by_uid_in)
|
|
39
|
+
|
|
40
|
+
if resource.repo_commit_sha:
|
|
41
|
+
filtered_by_repo_commit = msc.CodeRepositoryResource.filter(
|
|
42
|
+
repo_commit_sha=resource.repo_commit_sha
|
|
43
|
+
)
|
|
44
|
+
assert any(item.uid == resource.uid for item in filtered_by_repo_commit)
|
|
45
|
+
|
|
46
|
+
if resource.resource_type:
|
|
47
|
+
filtered_by_resource_type = msc.CodeRepositoryResource.filter(
|
|
48
|
+
resource_type=resource.resource_type
|
|
49
|
+
)
|
|
50
|
+
assert any(item.uid == resource.uid for item in filtered_by_resource_type)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
|
9
|
+
SKILL_ROOT = REPOSITORY_ROOT / "agent_scaffold" / "skills"
|
|
10
|
+
DOCUMENTATION_SITE_ROOT = "https://mainsequence-sdk.github.io/mainsequence-sdk"
|
|
11
|
+
# The wheel ships `agent_scaffold/skills` and no `docs/`, and the CLI copies those
|
|
12
|
+
# skills into a user's CodeRepository under `.agents/skills/<namespace>/`. There,
|
|
13
|
+
# `docs/` is that repository's own directory, so a skill that names an SDK
|
|
14
|
+
# documentation page by repository-relative path names a file the agent reading it
|
|
15
|
+
# cannot open. Skills cite the published documentation site instead.
|
|
16
|
+
SKILL_REPOSITORY_DOCUMENTATION_PATH = re.compile(r"`(docs/[A-Za-z0-9_./-]+\.md)`")
|
|
17
|
+
SKILL_DOCUMENTATION_URL = re.compile(re.escape(DOCUMENTATION_SITE_ROOT) + r"(/[A-Za-z0-9_./-]*)")
|
|
18
|
+
# Skills are copied into a CodeRepository under `.agents/skills/<namespace>/`, so
|
|
19
|
+
# a cross-skill reference maps back to a shipped `agent_scaffold/skills` path.
|
|
20
|
+
SKILL_CROSS_REFERENCE = re.compile(r"`\.agents/skills/[A-Za-z0-9_]+/([A-Za-z0-9_./-]+/SKILL\.md)`")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _shipped_scaffold_documents() -> tuple[Path, ...]:
|
|
24
|
+
"""Every Markdown file the wheel ships under `agent_scaffold`.
|
|
25
|
+
|
|
26
|
+
`AGENTS.md` ships beside the skills and several skills make it their first
|
|
27
|
+
mandatory read, so it carries the same reference obligations they do.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
return tuple(sorted((REPOSITORY_ROOT / "agent_scaffold").rglob("*.md")))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _documentation_page_for_url_path(url_path: str) -> Path | None:
|
|
34
|
+
"""Resolve a documentation-site URL path to the `docs/` page that renders it.
|
|
35
|
+
|
|
36
|
+
MkDocs serves directory URLs, so `/knowledge/meta_tables/migrations/` renders
|
|
37
|
+
`docs/knowledge/meta_tables/migrations.md` and `/knowledge/meta_tables/`
|
|
38
|
+
renders that directory's `index.md`. The transform is pure, so a skill's
|
|
39
|
+
documentation links are checkable without reaching the network.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
relative = url_path.strip("/")
|
|
43
|
+
if not relative:
|
|
44
|
+
return REPOSITORY_ROOT / "docs" / "index.md"
|
|
45
|
+
candidates = (
|
|
46
|
+
REPOSITORY_ROOT / "docs" / f"{relative}.md",
|
|
47
|
+
REPOSITORY_ROOT / "docs" / relative / "index.md",
|
|
48
|
+
)
|
|
49
|
+
return next((candidate for candidate in candidates if candidate.is_file()), None)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize(
|
|
53
|
+
"path",
|
|
54
|
+
_shipped_scaffold_documents(),
|
|
55
|
+
ids=lambda path: str(path.relative_to(REPOSITORY_ROOT)),
|
|
56
|
+
)
|
|
57
|
+
def test_skills_do_not_cite_repository_relative_documentation_paths(path: Path) -> None:
|
|
58
|
+
relative_path = path.relative_to(REPOSITORY_ROOT)
|
|
59
|
+
text = path.read_text(encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
repository_paths = sorted(set(SKILL_REPOSITORY_DOCUMENTATION_PATH.findall(text)))
|
|
62
|
+
|
|
63
|
+
assert not repository_paths, (
|
|
64
|
+
f"{relative_path} cites SDK documentation by repository-relative path: "
|
|
65
|
+
f"{repository_paths}. The wheel ships no `docs/`, so cite "
|
|
66
|
+
f"{DOCUMENTATION_SITE_ROOT}/... instead."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize(
|
|
71
|
+
"path",
|
|
72
|
+
_shipped_scaffold_documents(),
|
|
73
|
+
ids=lambda path: str(path.relative_to(REPOSITORY_ROOT)),
|
|
74
|
+
)
|
|
75
|
+
def test_skill_documentation_urls_render_an_existing_page(path: Path) -> None:
|
|
76
|
+
relative_path = path.relative_to(REPOSITORY_ROOT)
|
|
77
|
+
text = path.read_text(encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
unrendered = sorted(
|
|
80
|
+
{
|
|
81
|
+
url_path
|
|
82
|
+
for url_path in SKILL_DOCUMENTATION_URL.findall(text)
|
|
83
|
+
if _documentation_page_for_url_path(url_path) is None
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
assert not unrendered, (
|
|
88
|
+
f"{relative_path} links documentation-site paths that no `docs/` page "
|
|
89
|
+
f"renders: {unrendered}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize(
|
|
94
|
+
"path",
|
|
95
|
+
_shipped_scaffold_documents(),
|
|
96
|
+
ids=lambda path: str(path.relative_to(REPOSITORY_ROOT)),
|
|
97
|
+
)
|
|
98
|
+
def test_skill_cross_references_resolve(path: Path) -> None:
|
|
99
|
+
relative_path = path.relative_to(REPOSITORY_ROOT)
|
|
100
|
+
text = path.read_text(encoding="utf-8")
|
|
101
|
+
|
|
102
|
+
missing = sorted(
|
|
103
|
+
{
|
|
104
|
+
reference
|
|
105
|
+
for reference in SKILL_CROSS_REFERENCE.findall(text)
|
|
106
|
+
if not (SKILL_ROOT / reference).is_file()
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert not missing, f"{relative_path} references skills that are not shipped: {missing}"
|
|
@@ -1,50 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import pathlib
|
|
4
|
-
import sys
|
|
5
4
|
import tomllib
|
|
6
|
-
import types
|
|
7
5
|
|
|
8
|
-
import pytest
|
|
9
6
|
from packaging.requirements import Requirement
|
|
10
7
|
|
|
11
8
|
|
|
12
|
-
def _snapshot_mainsequence_modules() -> dict[str, types.ModuleType]:
|
|
13
|
-
return {
|
|
14
|
-
name: module
|
|
15
|
-
for name, module in sys.modules.items()
|
|
16
|
-
if name == "mainsequence" or name.startswith("mainsequence.")
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _reset_mainsequence_modules() -> None:
|
|
21
|
-
for name in tuple(sys.modules):
|
|
22
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
23
|
-
sys.modules.pop(name, None)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@pytest.fixture(autouse=True)
|
|
27
|
-
def _restore_mainsequence_modules_after_import_test():
|
|
28
|
-
snapshot = _snapshot_mainsequence_modules()
|
|
29
|
-
yield
|
|
30
|
-
_reset_mainsequence_modules()
|
|
31
|
-
sys.modules.update(snapshot)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _seed_mainsequence_packages() -> None:
|
|
35
|
-
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
|
36
|
-
pkg_root = repo_root / "mainsequence"
|
|
37
|
-
client_pkg_root = pkg_root / "client"
|
|
38
|
-
|
|
39
|
-
pkg = types.ModuleType("mainsequence")
|
|
40
|
-
pkg.__path__ = [str(pkg_root)]
|
|
41
|
-
sys.modules["mainsequence"] = pkg
|
|
42
|
-
|
|
43
|
-
client_pkg = types.ModuleType("mainsequence.client")
|
|
44
|
-
client_pkg.__path__ = [str(client_pkg_root)]
|
|
45
|
-
sys.modules["mainsequence.client"] = client_pkg
|
|
46
|
-
|
|
47
|
-
|
|
48
9
|
def test_streamlit_is_absent_from_dependency_contracts() -> None:
|
|
49
10
|
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
|
50
11
|
project = tomllib.loads((repo_root / "pyproject.toml").read_text(encoding="utf-8"))
|
|
@@ -43,15 +43,14 @@ def clock(monkeypatch):
|
|
|
43
43
|
return clock
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
@pytest.mark.parametrize("method", ["POST", "PUT", "PATCH", "DELETE"])
|
|
47
46
|
@pytest.mark.parametrize("error", [requests.ReadTimeout, requests.ConnectionError])
|
|
48
|
-
def test_write_transport_failure_is_not_replayed(monkeypatch,
|
|
47
|
+
def test_write_transport_failure_is_not_replayed(monkeypatch, error):
|
|
49
48
|
session = Mock(headers=requests.structures.CaseInsensitiveDict())
|
|
50
|
-
request =
|
|
49
|
+
request = session.post
|
|
51
50
|
request.side_effect = error("uncertain result")
|
|
52
51
|
monkeypatch.setattr(utils.time, "sleep", lambda _: None)
|
|
53
52
|
|
|
54
|
-
response = utils.make_request(session,
|
|
53
|
+
response = utils.make_request(session, "POST", "https://example.test/", None, time_out=1)
|
|
55
54
|
|
|
56
55
|
assert response.code == "expired"
|
|
57
56
|
assert request.call_count == 1
|
|
@@ -72,8 +71,10 @@ def test_programming_error_is_not_retried(monkeypatch):
|
|
|
72
71
|
assert session.get.call_count == 1
|
|
73
72
|
|
|
74
73
|
|
|
75
|
-
@pytest.mark.parametrize(
|
|
76
|
-
|
|
74
|
+
@pytest.mark.parametrize(
|
|
75
|
+
("method", "status"),
|
|
76
|
+
[("POST", 429), ("POST", 503), ("PUT", 503), ("PATCH", 503), ("DELETE", 503)],
|
|
77
|
+
)
|
|
77
78
|
def test_write_status_is_not_retried_by_adapter(monkeypatch, clock, method, status):
|
|
78
79
|
send = Mock(return_value=answer(status, {"Retry-After": "60"}))
|
|
79
80
|
monkeypatch.setattr(requests.adapters.HTTPAdapter, "send", send)
|
|
@@ -85,15 +86,14 @@ def test_write_status_is_not_retried_by_adapter(monkeypatch, clock, method, stat
|
|
|
85
86
|
assert clock.sleeps == []
|
|
86
87
|
|
|
87
88
|
|
|
88
|
-
@pytest.mark.parametrize("method", ["POST", "PUT", "PATCH", "DELETE"])
|
|
89
89
|
@pytest.mark.parametrize(
|
|
90
90
|
"error", [requests.ReadTimeout, requests.ConnectTimeout, requests.ConnectionError]
|
|
91
91
|
)
|
|
92
|
-
def test_adapter_never_retries_write_exceptions(monkeypatch, clock,
|
|
92
|
+
def test_adapter_never_retries_write_exceptions(monkeypatch, clock, error):
|
|
93
93
|
send = Mock(side_effect=error("failed"))
|
|
94
94
|
monkeypatch.setattr(requests.adapters.HTTPAdapter, "send", send)
|
|
95
95
|
with utils.build_session() as session, pytest.raises(error):
|
|
96
|
-
session.
|
|
96
|
+
session.post("https://example.test/", timeout=1)
|
|
97
97
|
|
|
98
98
|
assert send.call_count == 1
|
|
99
99
|
assert clock.sleeps == []
|
|
@@ -391,19 +391,6 @@ def slow_server(delay=0.15, status=200):
|
|
|
391
391
|
server.server_close()
|
|
392
392
|
|
|
393
393
|
|
|
394
|
-
@pytest.mark.parametrize("method", ["POST", "PUT", "PATCH", "DELETE"])
|
|
395
|
-
def test_slow_server_receives_exactly_one_write(method):
|
|
396
|
-
with slow_server() as (url, calls), utils.build_session() as session:
|
|
397
|
-
started = time.monotonic()
|
|
398
|
-
response = utils.make_request(session, method, url, None, time_out=0.05)
|
|
399
|
-
elapsed = time.monotonic() - started
|
|
400
|
-
assert response.code == "expired"
|
|
401
|
-
assert calls == [method]
|
|
402
|
-
assert elapsed < 0.15
|
|
403
|
-
time.sleep(0.16)
|
|
404
|
-
assert calls == [method]
|
|
405
|
-
|
|
406
|
-
|
|
407
394
|
def test_slow_get_uses_one_total_budget():
|
|
408
395
|
with (
|
|
409
396
|
slow_server(delay=0.04, status=503) as (url, calls),
|
|
@@ -5,31 +5,7 @@ import types
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
def _snapshot_mainsequence_modules() -> dict[str, types.ModuleType]:
|
|
10
|
-
return {
|
|
11
|
-
name: module
|
|
12
|
-
for name, module in sys.modules.items()
|
|
13
|
-
if name == "mainsequence" or name.startswith("mainsequence.")
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@pytest.fixture(autouse=True)
|
|
18
|
-
def _restore_mainsequence_modules():
|
|
19
|
-
"""Undo the stub packages this module installs into ``sys.modules``.
|
|
20
|
-
|
|
21
|
-
``_load_models_helpers_module`` replaces the real ``mainsequence`` packages
|
|
22
|
-
with stubs so ``models_helpers`` can be imported without the heavy client
|
|
23
|
-
stack. Without this fixture the stubs leak into every test module that runs
|
|
24
|
-
afterwards, and ``monkeypatch.setattr("mainsequence.client....")`` there
|
|
25
|
-
fails with ``module 'mainsequence' has no attribute 'client'``.
|
|
26
|
-
"""
|
|
27
|
-
snapshot = _snapshot_mainsequence_modules()
|
|
28
|
-
yield
|
|
29
|
-
for name in tuple(sys.modules):
|
|
30
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
31
|
-
sys.modules.pop(name, None)
|
|
32
|
-
sys.modules.update(snapshot)
|
|
8
|
+
pytestmark = pytest.mark.usefixtures("isolated_sdk_imports")
|
|
33
9
|
|
|
34
10
|
|
|
35
11
|
def _load_models_helpers_module():
|
|
@@ -137,8 +113,7 @@ def test_job_run_job_posts_to_canonical_action(monkeypatch):
|
|
|
137
113
|
assert captured == {
|
|
138
114
|
"r_type": "POST",
|
|
139
115
|
"url": (
|
|
140
|
-
"https://backend.test/api/v1/jobs/"
|
|
141
|
-
"7d0ab07c-d1c0-4b7f-9c69-3c1a41c0a4da/run-job/"
|
|
116
|
+
"https://backend.test/api/v1/jobs/" "7d0ab07c-d1c0-4b7f-9c69-3c1a41c0a4da/run-job/"
|
|
142
117
|
),
|
|
143
118
|
"payload": {"json": {"command_args": ["--name", "demo-from-cli"]}},
|
|
144
119
|
"timeout": 30,
|
|
@@ -1,66 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import importlib
|
|
4
3
|
import os
|
|
5
|
-
import pathlib
|
|
6
|
-
import sys
|
|
7
|
-
import types
|
|
8
4
|
|
|
9
5
|
import pytest
|
|
10
6
|
import requests
|
|
11
7
|
|
|
8
|
+
from tests._support import RETIRED_RUNTIME_CONTEXT_ENV_NAMES as _RUNTIME_CONTEXT_ENV_NAMES
|
|
9
|
+
from tests._support import load_sdk_submodule as _load_mainsequence_submodule
|
|
10
|
+
|
|
11
|
+
pytestmark = pytest.mark.usefixtures("isolated_sdk_imports", "clean_runtime_context_environment")
|
|
12
|
+
|
|
13
|
+
|
|
12
14
|
_REMOVED_DOMAIN_TOKEN = "PRO" + "JECT"
|
|
13
15
|
_UNSUPPORTED_REPOSITORY_UID_ENV = "MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_UID"
|
|
14
|
-
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV =
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
_UNSUPPORTED_REPOSITORY_NUMERIC_ID_ENV = (
|
|
18
|
-
"MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_ID"
|
|
19
|
-
)
|
|
16
|
+
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV = "MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_BRANCH_UID"
|
|
17
|
+
_UNSUPPORTED_REPOSITORY_NUMERIC_ID_ENV = "MAIN_SEQUENCE_" + _REMOVED_DOMAIN_TOKEN + "_ID"
|
|
20
18
|
_UNSUPPORTED_ENVIRONMENT_UID_ENV = (
|
|
21
19
|
"MAIN_SEQUENCE_ORGANIZATION_" + _REMOVED_DOMAIN_TOKEN + "_ENVIRONMENT_UID"
|
|
22
20
|
)
|
|
23
21
|
|
|
24
|
-
_RUNTIME_CONTEXT_ENV_NAMES = (
|
|
25
|
-
_UNSUPPORTED_REPOSITORY_UID_ENV,
|
|
26
|
-
_UNSUPPORTED_REPOSITORY_BRANCH_UID_ENV,
|
|
27
|
-
"MAINSEQUENCE_REPOSITORY_BRANCH",
|
|
28
|
-
_UNSUPPORTED_ENVIRONMENT_UID_ENV,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@pytest.fixture(autouse=True)
|
|
33
|
-
def _clear_runtime_context_environment():
|
|
34
|
-
original_modules = {
|
|
35
|
-
name: module
|
|
36
|
-
for name, module in sys.modules.items()
|
|
37
|
-
if name == "mainsequence" or name.startswith("mainsequence.")
|
|
38
|
-
}
|
|
39
|
-
for name in _RUNTIME_CONTEXT_ENV_NAMES:
|
|
40
|
-
os.environ.pop(name, None)
|
|
41
|
-
yield
|
|
42
|
-
for name in _RUNTIME_CONTEXT_ENV_NAMES:
|
|
43
|
-
os.environ.pop(name, None)
|
|
44
|
-
for name in tuple(sys.modules):
|
|
45
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
46
|
-
sys.modules.pop(name, None)
|
|
47
|
-
sys.modules.update(original_modules)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _load_mainsequence_submodule(module_name: str):
|
|
51
|
-
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
|
52
|
-
pkg_root = repo_root / "mainsequence"
|
|
53
|
-
|
|
54
|
-
for name in tuple(sys.modules):
|
|
55
|
-
if name == "mainsequence" or name.startswith("mainsequence."):
|
|
56
|
-
sys.modules.pop(name, None)
|
|
57
|
-
|
|
58
|
-
pkg = types.ModuleType("mainsequence")
|
|
59
|
-
pkg.__path__ = [str(pkg_root)]
|
|
60
|
-
sys.modules["mainsequence"] = pkg
|
|
61
|
-
|
|
62
|
-
return importlib.import_module(module_name)
|
|
63
|
-
|
|
64
22
|
|
|
65
23
|
def test_is_running_in_pod_uses_job_run_uid(monkeypatch):
|
|
66
24
|
runtime_flags = _load_mainsequence_submodule("mainsequence.runtime_flags")
|