mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 08:52:45 +00:00
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
|
|
# Target Services (Real implementations)
|
|
from services.bridge_token_lifecycle import BridgeScope, BridgeTokenStore, TokenStatus
|
|
from services.registry_quarantine import (
|
|
QuarantineState,
|
|
RegistryQuarantineError,
|
|
RegistryQuarantineStore,
|
|
)
|
|
from services.webhook_mapping import BUILTIN_PROFILES, apply_mapping
|
|
|
|
|
|
class TestR109ContractParity(unittest.TestCase):
|
|
"""
|
|
R109: Mock-to-Contract Migration & Parity Checks.
|
|
Verifies critical surfaces with real filesystem/state dependencies (no mocks where possible).
|
|
"""
|
|
|
|
def setUp(self):
|
|
self.test_dir = tempfile.mkdtemp()
|
|
self.maxDiff = None
|
|
# Clean environment
|
|
self.original_env = dict(os.environ)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.test_dir)
|
|
os.environ.clear()
|
|
os.environ.update(self.original_env)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# 1. Bridge Token Contract (Real Store)
|
|
# ----------------------------------------------------------------------
|
|
def test_bridge_contract_persistence(self):
|
|
"""
|
|
Contract: BridgeTokenStore persists headers to disk and survives reload.
|
|
"""
|
|
store = BridgeTokenStore(state_dir=self.test_dir)
|
|
|
|
# Act 1: Issue
|
|
t1 = store.issue_token("dev-1", ttl_sec=3600)
|
|
self.assertTrue(t1.token_id.startswith("bt_"))
|
|
|
|
# Act 2: Verify Persistence (Reload from disk)
|
|
store2 = BridgeTokenStore(state_dir=self.test_dir)
|
|
tokens = store2.list_tokens()
|
|
self.assertEqual(len(tokens), 1)
|
|
self.assertEqual(tokens[0].token_id, t1.token_id)
|
|
self.assertEqual(tokens[0].device_id, "dev-1")
|
|
|
|
def test_bridge_contract_fault_corrupt_state(self):
|
|
"""
|
|
Contract: Corrupt state file results in empty store (fail-closed/safe-reset)
|
|
rather than crash or unsafe state.
|
|
"""
|
|
# Create corrupt file
|
|
json_path = os.path.join(self.test_dir, "bridge_tokens.json")
|
|
with open(json_path, "w") as f:
|
|
f.write("{invalid-json...")
|
|
|
|
# Init store
|
|
store = BridgeTokenStore(state_dir=self.test_dir)
|
|
# Should be empty, logging error (which we don't capture here but verify state)
|
|
self.assertEqual(len(store.list_tokens()), 0)
|
|
|
|
# Should be usable (able to issue new tokens)
|
|
t_new = store.issue_token("dev-recovery")
|
|
self.assertTrue(t_new)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# 2. Registry Quarantine Contract (Feature Gate)
|
|
# ----------------------------------------------------------------------
|
|
def test_registry_feature_gate_contract(self):
|
|
"""
|
|
Contract: Registry operations raise Error if feature flag is disabled (Default).
|
|
Fail-Closed.
|
|
"""
|
|
# Ensure flag is OFF
|
|
if "OPENCLAW_ENABLE_REGISTRY_SYNC" in os.environ:
|
|
del os.environ["OPENCLAW_ENABLE_REGISTRY_SYNC"]
|
|
|
|
store = RegistryQuarantineStore(state_dir=self.test_dir)
|
|
|
|
# Verify read operations might work (list_entries) but write operations FAIL
|
|
# Actually `list_entries` doesn't check `_require_enabled` in code I read?
|
|
# Let's check `register_fetch` which does `self._require_enabled()`.
|
|
|
|
with self.assertRaises(RegistryQuarantineError) as cm:
|
|
store.register_fetch("pkg", "1.0", "http://src", "sha")
|
|
self.assertIn("disabled", str(cm.exception))
|
|
|
|
def test_registry_quarantine_persistence(self):
|
|
"""
|
|
Contract: Registry state persists across reloads (when enabled).
|
|
"""
|
|
os.environ["OPENCLAW_ENABLE_REGISTRY_SYNC"] = "1"
|
|
store = RegistryQuarantineStore(state_dir=self.test_dir)
|
|
|
|
store.register_fetch("pkg-a", "1.0", "http://a", "sha256_hash")
|
|
|
|
# Reload
|
|
store2 = RegistryQuarantineStore(state_dir=self.test_dir)
|
|
entry = store2.get_entry("pkg-a", "1.0")
|
|
self.assertIsNotNone(entry)
|
|
self.assertEqual(entry.state, QuarantineState.FETCHED.value)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# 3. Webhook Mapping Contract (Real Profiles)
|
|
# ----------------------------------------------------------------------
|
|
def test_webhook_builtin_contract(self):
|
|
"""
|
|
Contract: Built-in profiles function correctly on real sample inputs.
|
|
"""
|
|
# GitHub Push
|
|
profile = BUILTIN_PROFILES["github_push"]
|
|
sample_github = {
|
|
"repository": {"full_name": "user/repo"},
|
|
"ref": "refs/heads/main",
|
|
"sender": {"login": "monalisa"},
|
|
}
|
|
|
|
mapped, warnings = apply_mapping(profile, sample_github)
|
|
|
|
self.assertEqual(mapped["inputs"]["repo_name"], "user/repo")
|
|
self.assertEqual(mapped["inputs"]["ref"], "refs/heads/main")
|
|
self.assertEqual(mapped["inputs"]["actor"], "monalisa")
|
|
|
|
# Contract Verification:
|
|
# The built-in github_push profile does NOT include a template_id.
|
|
# This means raw mapping is successful, but it fails canonical schema validation.
|
|
self.assertNotIn("template_id", mapped)
|
|
|
|
# Verify schema fails as expected
|
|
from services.webhook_mapping import validate_canonical_schema
|
|
|
|
valid, errors = validate_canonical_schema(mapped)
|
|
self.assertFalse(valid)
|
|
self.assertIn("Missing required field: 'template_id'", errors)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|