mirror of
https://github.com/rookiestar28/ComfyUI-OpenClaw.git
synced 2026-08-14 00:48:07 +00:00
fix: remove leftover merge marker in pack registry
This commit is contained in:
@@ -40,11 +40,11 @@ class BoundedQueue(Generic[T]):
|
||||
def __init__(self, capacity: int) -> None:
|
||||
if capacity <= 0:
|
||||
raise ValueError("Capacity must be positive")
|
||||
|
||||
|
||||
self._capacity = capacity
|
||||
self._deque: Deque[T] = deque(maxlen=capacity)
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
# Metrics
|
||||
self._high_watermark = 0
|
||||
self._total_enqueued = 0
|
||||
@@ -64,14 +64,14 @@ class BoundedQueue(Generic[T]):
|
||||
self._total_dropped += 1
|
||||
self._last_drop_ts = time.time()
|
||||
dropped = True
|
||||
|
||||
|
||||
self._deque.append(item)
|
||||
self._total_enqueued += 1
|
||||
|
||||
|
||||
current_size = len(self._deque)
|
||||
if current_size > self._high_watermark:
|
||||
self._high_watermark = current_size
|
||||
|
||||
|
||||
return not dropped
|
||||
|
||||
def get_all(self) -> List[T]:
|
||||
@@ -88,7 +88,7 @@ class BoundedQueue(Generic[T]):
|
||||
high_watermark=self._high_watermark,
|
||||
total_enqueued=self._total_enqueued,
|
||||
total_dropped=self._total_dropped,
|
||||
last_drop_ts=self._last_drop_ts
|
||||
last_drop_ts=self._last_drop_ts,
|
||||
)
|
||||
|
||||
def clear(self) -> None:
|
||||
|
||||
@@ -3,7 +3,6 @@ import re
|
||||
import shutil
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
<<<<<<< HEAD
|
||||
from ..safe_io import resolve_under_root
|
||||
from .pack_archive import PackArchive, PackError
|
||||
from .pack_types import PackMetadata
|
||||
@@ -57,9 +56,7 @@ class PackRegistry:
|
||||
# A malicious pack.json could contain traversal sequences.
|
||||
_validate_pack_segment(name, "name")
|
||||
_validate_pack_segment(version, "version")
|
||||
target_dir = resolve_under_root(
|
||||
self.packs_dir, os.path.join(name, version)
|
||||
)
|
||||
target_dir = resolve_under_root(self.packs_dir, os.path.join(name, version))
|
||||
|
||||
if os.path.exists(target_dir):
|
||||
if not overwrite:
|
||||
@@ -76,9 +73,7 @@ class PackRegistry:
|
||||
def uninstall_pack(self, name: str, version: str) -> bool:
|
||||
_validate_pack_segment(name, "name")
|
||||
_validate_pack_segment(version, "version")
|
||||
target_dir = resolve_under_root(
|
||||
self.packs_dir, os.path.join(name, version)
|
||||
)
|
||||
target_dir = resolve_under_root(self.packs_dir, os.path.join(name, version))
|
||||
if os.path.exists(target_dir):
|
||||
shutil.rmtree(target_dir)
|
||||
# Clean up parent if empty
|
||||
@@ -118,9 +113,7 @@ class PackRegistry:
|
||||
def get_pack_path(self, name: str, version: str) -> Optional[str]:
|
||||
_validate_pack_segment(name, "name")
|
||||
_validate_pack_segment(version, "version")
|
||||
target_dir = resolve_under_root(
|
||||
self.packs_dir, os.path.join(name, version)
|
||||
)
|
||||
target_dir = resolve_under_root(self.packs_dir, os.path.join(name, version))
|
||||
if os.path.exists(target_dir):
|
||||
return target_dir
|
||||
return None
|
||||
|
||||
@@ -60,24 +60,24 @@ class PermissionEvaluator:
|
||||
def evaluate(self) -> List[PermissionResult]:
|
||||
"""Run all permission checks."""
|
||||
results = []
|
||||
|
||||
|
||||
# 1. State Directory
|
||||
results.append(self._check_state_dir())
|
||||
|
||||
# 2. Secrets File
|
||||
results.append(self._check_secrets_file())
|
||||
|
||||
|
||||
return results
|
||||
|
||||
def _check_state_dir(self) -> PermissionResult:
|
||||
"""Check state directory permissions."""
|
||||
path = self.state_dir
|
||||
if not path.exists():
|
||||
return PermissionResult(
|
||||
return PermissionResult(
|
||||
resource="state_dir",
|
||||
severity=PermissionSeverity.SKIP,
|
||||
message="State directory does not exist",
|
||||
code="perm.state_dir.missing"
|
||||
code="perm.state_dir.missing",
|
||||
)
|
||||
|
||||
# Common: Must be writable by owner/current user
|
||||
@@ -88,7 +88,7 @@ class PermissionEvaluator:
|
||||
severity=PermissionSeverity.FAIL,
|
||||
message=f"State directory not writable: {path}",
|
||||
code="perm.state_dir.not_writable",
|
||||
remediation="Ensure the process user has write access."
|
||||
remediation="Ensure the process user has write access.",
|
||||
)
|
||||
|
||||
# POSIX Hardening
|
||||
@@ -97,13 +97,17 @@ class PermissionEvaluator:
|
||||
mode = path.stat().st_mode
|
||||
if mode & stat.S_IWOTH:
|
||||
# World writable is CRITICAL in Hardened
|
||||
sev = PermissionSeverity.FAIL if self.profile == RuntimeProfile.HARDENED else PermissionSeverity.WARN
|
||||
sev = (
|
||||
PermissionSeverity.FAIL
|
||||
if self.profile == RuntimeProfile.HARDENED
|
||||
else PermissionSeverity.WARN
|
||||
)
|
||||
return PermissionResult(
|
||||
resource="state_dir",
|
||||
severity=sev,
|
||||
message="State directory is world-writable",
|
||||
message="State directory is world-writable",
|
||||
code="perm.state_dir.world_writable",
|
||||
remediation=f"chmod 700 {path}"
|
||||
remediation=f"chmod 700 {path}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to stat state dir: {e}")
|
||||
@@ -112,19 +116,19 @@ class PermissionEvaluator:
|
||||
resource="state_dir",
|
||||
severity=PermissionSeverity.PASS,
|
||||
message="State directory permissions OK",
|
||||
code="perm.state_dir.ok"
|
||||
code="perm.state_dir.ok",
|
||||
)
|
||||
|
||||
def _check_secrets_file(self) -> PermissionResult:
|
||||
"""Check secrets.json permissions."""
|
||||
path = self.state_dir / "secrets.json"
|
||||
|
||||
|
||||
if not path.exists():
|
||||
return PermissionResult(
|
||||
return PermissionResult(
|
||||
resource="secrets_file",
|
||||
severity=PermissionSeverity.PASS, # Not an error to not exist
|
||||
severity=PermissionSeverity.PASS, # Not an error to not exist
|
||||
message="Secrets file not present",
|
||||
code="perm.secrets.missing"
|
||||
code="perm.secrets.missing",
|
||||
)
|
||||
|
||||
if self.system != "Windows":
|
||||
@@ -132,13 +136,17 @@ class PermissionEvaluator:
|
||||
mode = path.stat().st_mode
|
||||
# Check World Readable/Writable
|
||||
if mode & (stat.S_IROTH | stat.S_IWOTH):
|
||||
sev = PermissionSeverity.FAIL if self.profile == RuntimeProfile.HARDENED else PermissionSeverity.WARN
|
||||
sev = (
|
||||
PermissionSeverity.FAIL
|
||||
if self.profile == RuntimeProfile.HARDENED
|
||||
else PermissionSeverity.WARN
|
||||
)
|
||||
return PermissionResult(
|
||||
resource="secrets_file",
|
||||
severity=sev,
|
||||
message="Secrets file is world-accessible",
|
||||
code="perm.secrets.world_accessible",
|
||||
remediation=f"chmod 600 {path}"
|
||||
remediation=f"chmod 600 {path}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to stat secrets file: {e}")
|
||||
@@ -147,7 +155,7 @@ class PermissionEvaluator:
|
||||
resource="secrets_file",
|
||||
severity=PermissionSeverity.PASS,
|
||||
message="Secrets file permissions OK",
|
||||
code="perm.secrets.ok"
|
||||
code="perm.secrets.ok",
|
||||
)
|
||||
|
||||
|
||||
@@ -158,13 +166,15 @@ def evaluate_startup_permissions() -> Tuple[bool, List[PermissionResult]]:
|
||||
"""
|
||||
evaluator = PermissionEvaluator()
|
||||
results = evaluator.evaluate()
|
||||
|
||||
|
||||
# Block startup if any FAIL result exists
|
||||
failures = [r for r in results if r.severity == PermissionSeverity.FAIL]
|
||||
if failures:
|
||||
logger.critical(f"Startup blocked by permission checks ({len(failures)} failures).")
|
||||
logger.critical(
|
||||
f"Startup blocked by permission checks ({len(failures)} failures)."
|
||||
)
|
||||
for f in failures:
|
||||
logger.critical(f" [{f.code}] {f.message} -> {f.remediation}")
|
||||
return False, results
|
||||
|
||||
|
||||
return True, results
|
||||
|
||||
+14
-12
@@ -1,31 +1,32 @@
|
||||
|
||||
import unittest
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from services.observability.backpressure import BoundedQueue
|
||||
|
||||
|
||||
class TestBoundedQueue(unittest.TestCase):
|
||||
|
||||
|
||||
def test_capacity_and_drops(self):
|
||||
"""Test strict capacity enforcement and drop counting."""
|
||||
q = BoundedQueue[int](capacity=2)
|
||||
|
||||
|
||||
# 1. Fill to capacity
|
||||
self.assertTrue(q.enqueue(1))
|
||||
self.assertTrue(q.enqueue(2))
|
||||
|
||||
|
||||
stats = q.stats()
|
||||
self.assertEqual(stats.current_size, 2)
|
||||
self.assertEqual(stats.high_watermark, 2)
|
||||
self.assertEqual(stats.total_dropped, 0)
|
||||
|
||||
|
||||
# 2. Overflow (Drop Oldest)
|
||||
self.assertFalse(q.enqueue(3))
|
||||
|
||||
|
||||
stats = q.stats()
|
||||
self.assertEqual(stats.current_size, 2)
|
||||
self.assertEqual(stats.total_dropped, 1)
|
||||
self.assertGreater(stats.last_drop_ts, 0)
|
||||
|
||||
|
||||
# Check content (1 should be dropped, 2 and 3 remain)
|
||||
items = q.get_all()
|
||||
self.assertEqual(items, [2, 3])
|
||||
@@ -33,18 +34,19 @@ class TestBoundedQueue(unittest.TestCase):
|
||||
def test_stats_tracking(self):
|
||||
"""Test cumulative stats logic."""
|
||||
q = BoundedQueue[str](capacity=5)
|
||||
|
||||
|
||||
q.enqueue("a")
|
||||
q.enqueue("b")
|
||||
self.assertEqual(q.stats().high_watermark, 2)
|
||||
|
||||
q.get_all() # Just reading doesn't change state
|
||||
|
||||
|
||||
q.get_all() # Just reading doesn't change state
|
||||
|
||||
q.clear()
|
||||
stats = q.stats()
|
||||
self.assertEqual(stats.current_size, 0)
|
||||
self.assertEqual(stats.high_watermark, 0)
|
||||
self.assertEqual(stats.total_enqueued, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+1
-1
@@ -6,13 +6,13 @@ import unittest
|
||||
import zipfile
|
||||
|
||||
from services.packs.pack_archive import PackArchive
|
||||
from services.packs.pack_registry import PackRegistry, _validate_pack_segment
|
||||
from services.packs.pack_manifest import (
|
||||
MAX_MANIFEST_FILES,
|
||||
PackError,
|
||||
validate_manifest_integrity,
|
||||
validate_pack_metadata,
|
||||
)
|
||||
from services.packs.pack_registry import PackRegistry, _validate_pack_segment
|
||||
from services.packs.pack_types import PackMetadata, PackType
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
|
||||
import unittest
|
||||
import stat
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from services.permission_posture import (
|
||||
PermissionEvaluator,
|
||||
PermissionResult,
|
||||
PermissionSeverity,
|
||||
evaluate_startup_permissions
|
||||
evaluate_startup_permissions,
|
||||
)
|
||||
from services.runtime_profile import RuntimeProfile
|
||||
|
||||
|
||||
class TestPermissionPosture(unittest.TestCase):
|
||||
|
||||
@patch("services.permission_posture.get_runtime_profile")
|
||||
@@ -19,15 +20,23 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
@patch("pathlib.Path.exists")
|
||||
@patch("os.access")
|
||||
@patch("pathlib.Path.stat")
|
||||
def test_posix_hardened_fail(self, mock_stat, mock_access, mock_exists, mock_system, mock_get_state, mock_get_profile):
|
||||
def test_posix_hardened_fail(
|
||||
self,
|
||||
mock_stat,
|
||||
mock_access,
|
||||
mock_exists,
|
||||
mock_system,
|
||||
mock_get_state,
|
||||
mock_get_profile,
|
||||
):
|
||||
"""Hardened profile fails on world-writable state dir."""
|
||||
# Setup
|
||||
mock_get_profile.return_value = RuntimeProfile.HARDENED
|
||||
mock_get_state.return_value = "/tmp/state"
|
||||
mock_system.return_value = "Linux"
|
||||
mock_exists.return_value = True
|
||||
mock_access.return_value = True # Writable by us
|
||||
|
||||
mock_access.return_value = True # Writable by us
|
||||
|
||||
# Mock world-writable mode
|
||||
mock_stat_res = MagicMock()
|
||||
mock_stat_res.st_mode = stat.S_IWOTH | stat.S_IRWXU
|
||||
@@ -48,7 +57,15 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
@patch("pathlib.Path.exists")
|
||||
@patch("os.access")
|
||||
@patch("pathlib.Path.stat")
|
||||
def test_posix_minimal_warn(self, mock_stat, mock_access, mock_exists, mock_system, mock_get_state, mock_get_profile):
|
||||
def test_posix_minimal_warn(
|
||||
self,
|
||||
mock_stat,
|
||||
mock_access,
|
||||
mock_exists,
|
||||
mock_system,
|
||||
mock_get_state,
|
||||
mock_get_profile,
|
||||
):
|
||||
"""Minimal profile warns on world-writable state dir."""
|
||||
# Setup
|
||||
mock_get_profile.return_value = RuntimeProfile.MINIMAL
|
||||
@@ -56,7 +73,7 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
mock_system.return_value = "Linux"
|
||||
mock_exists.return_value = True
|
||||
mock_access.return_value = True
|
||||
|
||||
|
||||
# Mock world-writable mode
|
||||
mock_stat_res = MagicMock()
|
||||
mock_stat_res.st_mode = stat.S_IWOTH | stat.S_IRWXU
|
||||
@@ -77,7 +94,15 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
@patch("pathlib.Path.exists")
|
||||
@patch("os.access")
|
||||
@patch("pathlib.Path.stat")
|
||||
def test_secrets_world_readable_hardened(self, mock_stat, mock_access, mock_exists, mock_system, mock_get_state, mock_get_profile):
|
||||
def test_secrets_world_readable_hardened(
|
||||
self,
|
||||
mock_stat,
|
||||
mock_access,
|
||||
mock_exists,
|
||||
mock_system,
|
||||
mock_get_state,
|
||||
mock_get_profile,
|
||||
):
|
||||
"""Hardened profile fails on world-readable secrets."""
|
||||
mock_get_profile.return_value = RuntimeProfile.HARDENED
|
||||
mock_get_state.return_value = "/tmp/state"
|
||||
@@ -89,10 +114,10 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
mock_stat_res = MagicMock()
|
||||
mock_stat_res.st_mode = stat.S_IROTH | stat.S_IRWXU
|
||||
mock_stat.return_value = mock_stat_res
|
||||
|
||||
|
||||
evaluator = PermissionEvaluator()
|
||||
results = evaluator.evaluate()
|
||||
|
||||
|
||||
# Check secrets result
|
||||
secret_res = next(r for r in results if r.resource == "secrets_file")
|
||||
self.assertEqual(secret_res.severity, PermissionSeverity.FAIL)
|
||||
@@ -103,20 +128,23 @@ class TestPermissionPosture(unittest.TestCase):
|
||||
@patch("services.permission_posture.platform.system")
|
||||
@patch("pathlib.Path.exists")
|
||||
@patch("os.access")
|
||||
def test_startup_gate_block(self, mock_access, mock_exists, mock_system, mock_get_state, mock_get_profile):
|
||||
def test_startup_gate_block(
|
||||
self, mock_access, mock_exists, mock_system, mock_get_state, mock_get_profile
|
||||
):
|
||||
"""Ensure evaluate_startup_permissions returns False on failures."""
|
||||
mock_get_profile.return_value = RuntimeProfile.HARDENED
|
||||
mock_get_state.return_value = "/tmp/state"
|
||||
mock_system.return_value = "Linux"
|
||||
mock_exists.return_value = True
|
||||
|
||||
|
||||
# Fail access check (critical for all profiles)
|
||||
mock_access.return_value = False
|
||||
|
||||
mock_access.return_value = False
|
||||
|
||||
allowed, results = evaluate_startup_permissions()
|
||||
|
||||
|
||||
self.assertFalse(allowed)
|
||||
self.assertTrue(any(r.severity == PermissionSeverity.FAIL for r in results))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from services.permission_posture import PermissionResult, PermissionSeverity
|
||||
from services.security_gate import SecurityGate, enforce_startup_gate
|
||||
from services.permission_posture import PermissionResult, PermissionSeverity
|
||||
|
||||
|
||||
class TestSecurityGatePermissions(unittest.TestCase):
|
||||
|
||||
|
||||
@patch("services.security_gate.is_hardened_mode")
|
||||
@patch("services.permission_posture.evaluate_startup_permissions")
|
||||
@patch("services.access_control.is_auth_configured") # Patch source module
|
||||
@patch("services.access_control.is_auth_configured") # Patch source module
|
||||
@patch("services.redaction.redact_text")
|
||||
def test_gate_fails_on_permission_error(self, mock_redact, mock_auth, mock_eval_perms, mock_hardened):
|
||||
def test_gate_fails_on_permission_error(
|
||||
self, mock_redact, mock_auth, mock_eval_perms, mock_hardened
|
||||
):
|
||||
"""Gate should report failure if permission check fails in Hardened mode."""
|
||||
# Setup
|
||||
mock_hardened.return_value = True
|
||||
mock_auth.return_value = True # Auth OK
|
||||
mock_redact.return_value = "redacted" # Redaction OK
|
||||
|
||||
mock_auth.return_value = True # Auth OK
|
||||
mock_redact.return_value = "redacted" # Redaction OK
|
||||
|
||||
# Mock permission failure
|
||||
fail_res = PermissionResult(
|
||||
resource="test",
|
||||
severity=PermissionSeverity.FAIL,
|
||||
message="Test Perm Fail",
|
||||
code="perm.test.fail"
|
||||
code="perm.test.fail",
|
||||
)
|
||||
mock_eval_perms.return_value = (False, [fail_res])
|
||||
|
||||
|
||||
# Execute
|
||||
passed, reasons = SecurityGate.verify_mandatory_controls()
|
||||
|
||||
|
||||
# Assert
|
||||
self.assertFalse(passed)
|
||||
self.assertTrue(any("Test Perm Fail" in r for r in reasons))
|
||||
@@ -37,21 +40,24 @@ class TestSecurityGatePermissions(unittest.TestCase):
|
||||
@patch("services.permission_posture.evaluate_startup_permissions")
|
||||
@patch("services.access_control.is_auth_configured")
|
||||
@patch("services.redaction.redact_text")
|
||||
def test_gate_raise_exception(self, mock_redact, mock_auth, mock_eval_perms, mock_hardened):
|
||||
def test_gate_raise_exception(
|
||||
self, mock_redact, mock_auth, mock_eval_perms, mock_hardened
|
||||
):
|
||||
"""enforce_startup_gate should raise RuntimeError on failure."""
|
||||
mock_hardened.return_value = True
|
||||
mock_auth.return_value = True
|
||||
|
||||
|
||||
fail_res = PermissionResult(
|
||||
resource="test",
|
||||
severity=PermissionSeverity.FAIL,
|
||||
message="Test Perm Fail",
|
||||
code="perm.test.fail"
|
||||
code="perm.test.fail",
|
||||
)
|
||||
mock_eval_perms.return_value = (False, [fail_res])
|
||||
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
enforce_startup_gate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
|
||||
import unittest
|
||||
import json
|
||||
import unittest
|
||||
from dataclasses import asdict
|
||||
|
||||
from services.job_events import JobEvent, JobEventStore, JobEventType
|
||||
from services.operator_doctor import CheckResult, DoctorReport, Severity
|
||||
from services.security_doctor import SecurityCheckResult, SecurityReport, SecuritySeverity
|
||||
from services.job_events import JobEvent, JobEventType, JobEventStore
|
||||
from services.security_doctor import (
|
||||
SecurityCheckResult,
|
||||
SecurityReport,
|
||||
SecuritySeverity,
|
||||
)
|
||||
|
||||
|
||||
class TestWP0ContractBaseline(unittest.TestCase):
|
||||
"""
|
||||
@@ -20,7 +25,7 @@ class TestWP0ContractBaseline(unittest.TestCase):
|
||||
severity=Severity.PASS.value,
|
||||
message="Test Message",
|
||||
detail="Details",
|
||||
remediation="Fix it"
|
||||
remediation="Fix it",
|
||||
)
|
||||
d = result.to_dict()
|
||||
self.assertEqual(d["name"], "test-check")
|
||||
@@ -44,13 +49,13 @@ class TestWP0ContractBaseline(unittest.TestCase):
|
||||
message="Security Fail",
|
||||
category="endpoint",
|
||||
detail="Detail",
|
||||
remediation="Remedy"
|
||||
remediation="Remedy",
|
||||
)
|
||||
d = result.to_dict()
|
||||
self.assertEqual(d["name"], "sec-check")
|
||||
self.assertEqual(d["severity"], "fail")
|
||||
self.assertEqual(d["category"], "endpoint")
|
||||
|
||||
|
||||
report = SecurityReport()
|
||||
report.add(result)
|
||||
rd = report.to_dict()
|
||||
@@ -60,25 +65,26 @@ class TestWP0ContractBaseline(unittest.TestCase):
|
||||
def test_job_event_structure(self):
|
||||
"""Verify Job Event structure and basic ring buffer behavior."""
|
||||
store = JobEventStore(max_size=2)
|
||||
|
||||
|
||||
# Emit 1
|
||||
e1 = store.emit(JobEventType.QUEUED, "p1")
|
||||
self.assertEqual(e1.seq, 1)
|
||||
self.assertEqual(e1.event_type, "queued")
|
||||
self.assertEqual(e1.prompt_id, "p1")
|
||||
|
||||
|
||||
# Emit 2
|
||||
e2 = store.emit(JobEventType.RUNNING, "p1")
|
||||
self.assertEqual(e2.seq, 2)
|
||||
|
||||
|
||||
# Emit 3 (Should evict 1)
|
||||
e3 = store.emit(JobEventType.COMPLETED, "p1")
|
||||
self.assertEqual(e3.seq, 3)
|
||||
|
||||
|
||||
events = store.events_since(0)
|
||||
self.assertEqual(len(events), 2)
|
||||
self.assertEqual(events[0].seq, 2)
|
||||
self.assertEqual(events[1].seq, 3)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -232,4 +232,3 @@ class QueueMonitor {
|
||||
export const moltbotUI = new MoltbotUI();
|
||||
const monitor = new QueueMonitor(moltbotUI);
|
||||
monitor.start();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user