feat: initialize XAS test console with native execution
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from console.settings import Settings
|
||||
from console.native_plans import build_plan, profile_for, validate_options, public_execution
|
||||
from console.native_io import SecretFilter
|
||||
|
||||
|
||||
class NativePlanTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temp.name)
|
||||
source = self.root / "original"
|
||||
source.mkdir()
|
||||
(source / "logs").mkdir()
|
||||
self.entry = source / "test.py"
|
||||
self.entry.write_text("print('original')\n")
|
||||
self.settings = Settings(self.root / "runtime", [{"id": "original", "path": str(source)}])
|
||||
self.profile = {"id": "test", "source_group": "original", "entry": "test.py", "cwd": str(source),
|
||||
"interpreter": sys.executable, "user": "root", "arguments": ["{concurrency}", "{output_file}"],
|
||||
"parameters": [{"key": "concurrency", "label": "并发", "type": "integer", "default": 2, "min": 1, "max": 8},
|
||||
{"key": "configuration", "type": "select", "default": "known", "options": [{"value": "known", "label": "已登记配置"}]}],
|
||||
"impact": {"level": "service", "title": "切换服务", "details": ["可能停止旧服务"], "requires_confirmation": True},
|
||||
"environment": {}, "credential_files": [], "interactive": False, "output_paths": [str(source / "logs" / "console-{run_id}*")]}
|
||||
self.settings.native_catalog = {"profiles": [self.profile], "targets": []}
|
||||
self.script = {"id": "a" * 32, "kind": "external", "source_group": "original", "source_path": str(self.entry),
|
||||
"sha256": hashlib.sha256(self.entry.read_bytes()).hexdigest(), "config": {"native_options": {}, "timeout_seconds": 300}, "language": "python"}
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def test_plan_uses_original_file_and_typed_argv_without_shell_join(self):
|
||||
before = self.entry.read_bytes()
|
||||
plan = build_plan(self.settings, self.script, run_id="b" * 32, check_host=False)
|
||||
self.assertEqual(plan["argv"][:2], [sys.executable, str(self.entry)])
|
||||
self.assertEqual(plan["argv"][2], "2")
|
||||
self.assertIn("console-" + "b" * 32, plan["argv"][3])
|
||||
self.assertEqual(plan["cwd"], str(self.entry.parent))
|
||||
self.assertEqual(self.entry.read_bytes(), before)
|
||||
|
||||
def test_unregistered_uploaded_and_replaced_entries_cannot_run_natively(self):
|
||||
for change in ({"kind": "managed"}, {"source_path": str(self.root / "unknown.py")}, {"source_group": "other"}):
|
||||
self.assertIsNone(profile_for(self.settings, {**self.script, **change}))
|
||||
|
||||
def test_parameters_reject_injection_unknown_keys_bool_and_out_of_range(self):
|
||||
for value in ({"configuration": "../../etc/profile"}, {"configuration": "known;id"},
|
||||
{"concurrency": True}, {"concurrency": 9}, {"command": "id"}):
|
||||
with self.assertRaises(ValueError):
|
||||
validate_options(self.profile, value)
|
||||
|
||||
def test_confirmation_changes_with_options_source_or_recipe(self):
|
||||
first = build_plan(self.settings, self.script, check_host=False)["plan_token"]
|
||||
self.script["config"]["native_options"] = {"concurrency": 3}
|
||||
second = build_plan(self.settings, self.script, check_host=False)["plan_token"]
|
||||
self.assertNotEqual(first, second)
|
||||
self.profile["impact"]["details"].append("使用全部 GPU")
|
||||
self.assertNotEqual(second, build_plan(self.settings, self.script, check_host=False)["plan_token"])
|
||||
self.entry.write_text("print('changed')\n")
|
||||
with self.assertRaises(ValueError):
|
||||
build_plan(self.settings, self.script, check_host=False)
|
||||
|
||||
def test_credentials_not_in_public_execution(self):
|
||||
self.profile["credential_files"] = [str(self.root / "private.env")]
|
||||
visible = public_execution(self.settings, self.script)
|
||||
self.assertEqual(visible["mode"], "native")
|
||||
self.assertNotIn("credential_files", visible)
|
||||
self.assertNotIn("private.env", json.dumps(visible))
|
||||
|
||||
def test_stream_filter_redacts_secret_split_across_reads(self):
|
||||
secret = "dummy-credential-abcdefghijk"
|
||||
filt = SecretFilter([secret])
|
||||
output = filt.feed(b"hello\nkey=dummy-cred")
|
||||
output += filt.feed(b"ential-abcdefghijk\nworld\n")
|
||||
output += filt.finish()
|
||||
self.assertNotIn(secret, output.decode())
|
||||
self.assertIn("[REDACTED]", output.decode())
|
||||
self.assertIn("world", output.decode())
|
||||
|
||||
def test_ansi_split_inside_secret_cannot_reconstruct_plaintext(self):
|
||||
secret = "dummy-credential-abcdefghijk"
|
||||
filt = SecretFilter([secret])
|
||||
result = filt.feed(b"dummy-cred\x1b[")
|
||||
result += filt.feed(b"31mential-abcdefghijk\x1b[0m\n")
|
||||
result += filt.finish()
|
||||
self.assertNotIn(secret.encode(), result)
|
||||
self.assertIn(b"[REDACTED]", result)
|
||||
Reference in New Issue
Block a user