dsh-biocore 0.2.0 → 0.3.0
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.
- package/README.md +11 -0
- package/lib/core-worker.js +268 -0
- package/lib/index.js +160 -70
- package/lib/modules/autonomy.js +125 -0
- package/lib/modules/cache-stats.js +48 -0
- package/lib/modules/core-turn.js +24 -6
- package/lib/modules/expression-inject.js +0 -10
- package/lib/modules/expression-refresh.js +0 -9
- package/lib/modules/memory-capture.js +2 -1
- package/lib/modules/memory-inject.js +50 -5
- package/lib/modules/memory-tools.js +7 -4
- package/lib/modules/python-env.js +46 -0
- package/lib/modules/safety-gate.js +0 -3
- package/lib/modules/series.js +54 -0
- package/lib/modules/switch.js +16 -0
- package/lib/py-bridge.js +81 -15
- package/package.json +1 -1
- package/python/biocore_memory/core/config.py +138 -127
- package/python/biocore_memory/engine.py +2653 -2476
- package/python/biocore_memory/recall/embedding.py +129 -0
- package/python/biocore_memory/recall/feedback.py +202 -0
- package/python/biocore_model/preference_drift.py +255 -0
- package/python/biocore_model/tests/__init__.py +1 -0
- package/python/biocore_model/tests/core_mode_ui_check.mjs +63 -0
- package/python/biocore_model/tests/core_status_check.mjs +29 -0
- package/python/biocore_model/tests/dom_safe_check.mjs +22 -0
- package/python/biocore_model/tests/leak_samples_run.py +18 -0
- package/python/biocore_model/tests/orb_ring_check.mjs +20 -0
- package/python/biocore_model/tests/panel_html_check.mjs +63 -0
- package/python/biocore_model/tests/persona_flow_check.mjs +17 -0
- package/python/biocore_model/tests/render_guard_check.mjs +19 -0
- package/python/biocore_model/tests/switch_groups_check.mjs +94 -0
- package/python/biocore_model/tests/tabs_state_check.mjs +24 -0
- package/python/biocore_model/tests/test_affective_labor.py +102 -0
- package/python/biocore_model/tests/test_appraisal.py +137 -0
- package/python/biocore_model/tests/test_appraisal_action.py +55 -0
- package/python/biocore_model/tests/test_arch_convergence.py +35 -0
- package/python/biocore_model/tests/test_attachment_behavior.py +95 -0
- package/python/biocore_model/tests/test_attachment_regulation.py +63 -0
- package/python/biocore_model/tests/test_attachment_state.py +29 -0
- package/python/biocore_model/tests/test_attribution.py +56 -0
- package/python/biocore_model/tests/test_attributor.py +108 -0
- package/python/biocore_model/tests/test_autonomic.py +69 -0
- package/python/biocore_model/tests/test_autonomy_accumulator.py +84 -0
- package/python/biocore_model/tests/test_autonomy_affect.py +62 -0
- package/python/biocore_model/tests/test_autonomy_blame.py +44 -0
- package/python/biocore_model/tests/test_autonomy_caps.py +29 -0
- package/python/biocore_model/tests/test_autonomy_check.py +40 -0
- package/python/biocore_model/tests/test_autonomy_clingy.py +41 -0
- package/python/biocore_model/tests/test_autonomy_cooldown_selfcontact.py +138 -0
- package/python/biocore_model/tests/test_autonomy_frustration.py +42 -0
- package/python/biocore_model/tests/test_autonomy_hourly_limit.py +46 -0
- package/python/biocore_model/tests/test_autonomy_loop.py +64 -0
- package/python/biocore_model/tests/test_autonomy_mode.py +37 -0
- package/python/biocore_model/tests/test_autonomy_model.py +60 -0
- package/python/biocore_model/tests/test_autonomy_model_v2.py +68 -0
- package/python/biocore_model/tests/test_autonomy_module_text.py +104 -0
- package/python/biocore_model/tests/test_autonomy_params.py +163 -0
- package/python/biocore_model/tests/test_autonomy_reason.py +34 -0
- package/python/biocore_model/tests/test_autonomy_self.py +153 -0
- package/python/biocore_model/tests/test_autonomy_send_gate.py +38 -0
- package/python/biocore_model/tests/test_autonomy_silence.py +37 -0
- package/python/biocore_model/tests/test_autonomy_status.py +35 -0
- package/python/biocore_model/tests/test_autonomy_test_trigger.py +24 -0
- package/python/biocore_model/tests/test_autonomy_tick.py +148 -0
- package/python/biocore_model/tests/test_autonomy_v4.py +64 -0
- package/python/biocore_model/tests/test_autonomy_v5.py +43 -0
- package/python/biocore_model/tests/test_autonomy_v7.py +48 -0
- package/python/biocore_model/tests/test_autonomy_v_theta.py +85 -0
- package/python/biocore_model/tests/test_batch1_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch2_wiring.py +231 -0
- package/python/biocore_model/tests/test_batch3_wiring.py +223 -0
- package/python/biocore_model/tests/test_bio_tick.py +202 -0
- package/python/biocore_model/tests/test_boundary.py +183 -0
- package/python/biocore_model/tests/test_boundary_gate.py +295 -0
- package/python/biocore_model/tests/test_brain_split.py +99 -0
- package/python/biocore_model/tests/test_co_recall.py +350 -0
- package/python/biocore_model/tests/test_commitment_extractor.py +197 -0
- package/python/biocore_model/tests/test_compassion_fatigue.py +84 -0
- package/python/biocore_model/tests/test_connection_bids.py +69 -0
- package/python/biocore_model/tests/test_core_bridge.py +88 -0
- package/python/biocore_model/tests/test_core_bridge_episode.py +14 -0
- package/python/biocore_model/tests/test_core_bridge_forget.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_identity.py +15 -0
- package/python/biocore_model/tests/test_core_bridge_media.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_relation.py +17 -0
- package/python/biocore_model/tests/test_core_bridge_summary.py +17 -0
- package/python/biocore_model/tests/test_core_client.py +156 -0
- package/python/biocore_model/tests/test_core_commands.py +146 -0
- package/python/biocore_model/tests/test_core_mode.py +161 -0
- package/python/biocore_model/tests/test_core_state.py +240 -0
- package/python/biocore_model/tests/test_criticality.py +61 -0
- package/python/biocore_model/tests/test_cross_session_copy.py +67 -0
- package/python/biocore_model/tests/test_debug_values.py +169 -0
- package/python/biocore_model/tests/test_defense_mechanisms.py +177 -0
- package/python/biocore_model/tests/test_delta_budget.py +157 -0
- package/python/biocore_model/tests/test_diag_trace.py +201 -0
- package/python/biocore_model/tests/test_directives_compat.py +698 -0
- package/python/biocore_model/tests/test_dissonance.py +97 -0
- package/python/biocore_model/tests/test_drift_sign.py +87 -0
- package/python/biocore_model/tests/test_embedding_fallback.py +277 -0
- package/python/biocore_model/tests/test_embedding_local.py +279 -0
- package/python/biocore_model/tests/test_embedding_mode.py +175 -0
- package/python/biocore_model/tests/test_embedding_modes.py +342 -0
- package/python/biocore_model/tests/test_emotion_geometry.py +114 -0
- package/python/biocore_model/tests/test_emotion_probe.py +214 -0
- package/python/biocore_model/tests/test_emotion_probe_concurrency.py +128 -0
- package/python/biocore_model/tests/test_entity_graph.py +198 -0
- package/python/biocore_model/tests/test_entity_resolve.py +446 -0
- package/python/biocore_model/tests/test_envy.py +173 -0
- package/python/biocore_model/tests/test_exit_or_silence.py +209 -0
- package/python/biocore_model/tests/test_expr_guard.py +1 -0
- package/python/biocore_model/tests/test_expr_intent.py +66 -0
- package/python/biocore_model/tests/test_expr_state.py +66 -0
- package/python/biocore_model/tests/test_expr_template.py +71 -0
- package/python/biocore_model/tests/test_expr_tone.py +41 -0
- package/python/biocore_model/tests/test_extreme_scenarios.py +147 -0
- package/python/biocore_model/tests/test_feedback_flow.py +185 -0
- package/python/biocore_model/tests/test_feedback_guard.py +65 -0
- package/python/biocore_model/tests/test_feedback_llm.py +59 -0
- package/python/biocore_model/tests/test_feedback_llm_inferred.py +140 -0
- package/python/biocore_model/tests/test_feedback_memory.py +63 -0
- package/python/biocore_model/tests/test_feedback_user.py +61 -0
- package/python/biocore_model/tests/test_frustration_curve.py +66 -0
- package/python/biocore_model/tests/test_global_bio_state.py +116 -0
- package/python/biocore_model/tests/test_governance.py +181 -0
- package/python/biocore_model/tests/test_granularity.py +133 -0
- package/python/biocore_model/tests/test_graph_extract.py +224 -0
- package/python/biocore_model/tests/test_graph_recall.py +226 -0
- package/python/biocore_model/tests/test_graph_sqlite.py +298 -0
- package/python/biocore_model/tests/test_growth.py +301 -0
- package/python/biocore_model/tests/test_intent_render.py +60 -0
- package/python/biocore_model/tests/test_intero_regulation.py +224 -0
- package/python/biocore_model/tests/test_intero_style.py +146 -0
- package/python/biocore_model/tests/test_item3_numeric.py +105 -0
- package/python/biocore_model/tests/test_item6_numeric.py +116 -0
- package/python/biocore_model/tests/test_layered_memory.py +174 -0
- package/python/biocore_model/tests/test_leak_guard.py +75 -0
- package/python/biocore_model/tests/test_learning_probe.py +93 -0
- package/python/biocore_model/tests/test_life_loop.py +222 -0
- package/python/biocore_model/tests/test_life_loop_10s.py +131 -0
- package/python/biocore_model/tests/test_local_embedding_health.py +258 -0
- package/python/biocore_model/tests/test_memory_affect_encoding.py +79 -0
- package/python/biocore_model/tests/test_memory_backend.py +332 -0
- package/python/biocore_model/tests/test_memory_dedup.py +316 -0
- package/python/biocore_model/tests/test_memory_dual.py +219 -0
- package/python/biocore_model/tests/test_memory_dual_wiring.py +219 -0
- package/python/biocore_model/tests/test_memory_feedback.py +241 -0
- package/python/biocore_model/tests/test_memory_fixes.py +84 -0
- package/python/biocore_model/tests/test_memory_forgetting.py +58 -0
- package/python/biocore_model/tests/test_memory_inject.py +211 -0
- package/python/biocore_model/tests/test_memory_mood_recall.py +118 -0
- package/python/biocore_model/tests/test_memory_store.py +372 -0
- package/python/biocore_model/tests/test_metacog.py +103 -0
- package/python/biocore_model/tests/test_migrate.py +245 -0
- package/python/biocore_model/tests/test_migrate_store.py +194 -0
- package/python/biocore_model/tests/test_mind_wander.py +65 -0
- package/python/biocore_model/tests/test_mood.py +35 -0
- package/python/biocore_model/tests/test_narrative.py +114 -0
- package/python/biocore_model/tests/test_needs_esteem.py +51 -0
- package/python/biocore_model/tests/test_needs_events.py +205 -0
- package/python/biocore_model/tests/test_needs_sleep.py +54 -0
- package/python/biocore_model/tests/test_noise_bank.py +54 -0
- package/python/biocore_model/tests/test_numeric_schema.py +98 -0
- package/python/biocore_model/tests/test_output_protocol.py +65 -0
- package/python/biocore_model/tests/test_page_debug.py +147 -0
- package/python/biocore_model/tests/test_page_status_autonomy.py +215 -0
- package/python/biocore_model/tests/test_pipeline.py +345 -0
- package/python/biocore_model/tests/test_pipeline_api.py +192 -0
- package/python/biocore_model/tests/test_pipeline_mode.py +222 -0
- package/python/biocore_model/tests/test_plugin_config.py +85 -0
- package/python/biocore_model/tests/test_policy_learning.py +116 -0
- package/python/biocore_model/tests/test_possible_selves.py +88 -0
- package/python/biocore_model/tests/test_predictor.py +227 -0
- package/python/biocore_model/tests/test_preference_drift.py +142 -0
- package/python/biocore_model/tests/test_preset.py +142 -0
- package/python/biocore_model/tests/test_proactive_context.py +70 -0
- package/python/biocore_model/tests/test_proactive_history.py +90 -0
- package/python/biocore_model/tests/test_proactive_memory.py +141 -0
- package/python/biocore_model/tests/test_proactive_send_record.py +88 -0
- package/python/biocore_model/tests/test_probe_contract.py +71 -0
- package/python/biocore_model/tests/test_prospective.py +154 -0
- package/python/biocore_model/tests/test_provider_fallback.py +89 -0
- package/python/biocore_model/tests/test_reconstruction.py +111 -0
- package/python/biocore_model/tests/test_regulation_strategies.py +228 -0
- package/python/biocore_model/tests/test_relation_stage.py +114 -0
- package/python/biocore_model/tests/test_relationship_stage.py +59 -0
- package/python/biocore_model/tests/test_release_140.py +55 -0
- package/python/biocore_model/tests/test_repair_after_boundary.py +249 -0
- package/python/biocore_model/tests/test_reply_limit.py +86 -0
- package/python/biocore_model/tests/test_reply_limit_persist.py +48 -0
- package/python/biocore_model/tests/test_reply_split.py +52 -0
- package/python/biocore_model/tests/test_reply_split_astrbot.py +66 -0
- package/python/biocore_model/tests/test_reply_split_inject.py +53 -0
- package/python/biocore_model/tests/test_rerank.py +229 -0
- package/python/biocore_model/tests/test_rollback.py +175 -0
- package/python/biocore_model/tests/test_runtime.py +175 -0
- package/python/biocore_model/tests/test_runtime_active.py +205 -0
- package/python/biocore_model/tests/test_runtime_pipeline.py +1583 -0
- package/python/biocore_model/tests/test_runtime_proactive.py +231 -0
- package/python/biocore_model/tests/test_scheduler.py +79 -0
- package/python/biocore_model/tests/test_secrecy.py +105 -0
- package/python/biocore_model/tests/test_self_esteem.py +45 -0
- package/python/biocore_model/tests/test_self_intent.py +347 -0
- package/python/biocore_model/tests/test_self_interest.py +183 -0
- package/python/biocore_model/tests/test_self_layer_wiring.py +198 -0
- package/python/biocore_model/tests/test_self_verification.py +113 -0
- package/python/biocore_model/tests/test_situation.py +142 -0
- package/python/biocore_model/tests/test_sleep_circadian.py +41 -0
- package/python/biocore_model/tests/test_social_pain.py +49 -0
- package/python/biocore_model/tests/test_social_repair.py +67 -0
- package/python/biocore_model/tests/test_social_theory.py +78 -0
- package/python/biocore_model/tests/test_source_monitoring.py +204 -0
- package/python/biocore_model/tests/test_speak_like_group.py +36 -0
- package/python/biocore_model/tests/test_stages.py +226 -0
- package/python/biocore_model/tests/test_state_tag.py +86 -0
- package/python/biocore_model/tests/test_template_expr.py +65 -0
- package/python/biocore_model/tests/test_text_inference.py +70 -0
- package/python/biocore_model/tests/test_theta_params.py +115 -0
- package/python/biocore_model/tests/test_topic_control.py +200 -0
- package/python/biocore_model/tests/test_turn_memory.py +105 -0
- package/python/biocore_model/tests/test_turn_memory_panel.py +174 -0
- package/python/biocore_model/tests/test_ui_settings.py +84 -0
- package/python/biocore_model/tests/test_ui_settings_authority.py +179 -0
- package/python/biocore_model/tests/test_user_appraisal_async.py +155 -0
- package/python/biocore_model/tests/test_user_classify.py +122 -0
- package/python/biocore_model/tests/test_value_order.py +117 -0
- package/python/biocore_model/tests/test_voice_bank.py +105 -0
- package/python/biocore_model/tests/test_waiting_sense.py +216 -0
- package/python/biocore_model/tests/test_x_directives.py +101 -0
- package/python/biocore_model/tests/ui_compat_check.mjs +22 -0
- package/python/biocore_model/tests/view_mode_check.mjs +17 -0
- package/python/biocore_runtime/autonomy.py +208 -0
- package/python/biocore_runtime/core_state.py +302 -0
- package/python/biocore_runtime/governance.py +495 -0
- package/python/biocore_runtime/growth.py +528 -0
- package/python/biocore_runtime/pipeline.py +850 -23
- package/python/biocore_runtime/relationship_state.py +214 -0
- package/python/biocore_runtime/self_layer.py +103 -0
- package/python/biocore_runtime/service.py +162 -0
- package/python/biocore_runtime/standby.py +472 -0
- package/ui/panel-layout.js +83 -39
- package/ui/panel.css +885 -743
- package/ui/panel.html +356 -296
- package/ui/panel.js +739 -865
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""page_status autonomy: fake plugin variants ensure data.autonomy.state carries V/theta.
|
|
3
|
+
|
|
4
|
+
Regression for the live bug: when self.data_dir is missing at page_status time,
|
|
5
|
+
ui_settings.base_dir fell back to an old directory whose accumulator had None
|
|
6
|
+
values, overwriting the real state. The fix resolves the authority dir from
|
|
7
|
+
_data_dir/DATA_DIR and merges the accumulator outside the state try-block.
|
|
8
|
+
"""
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import tempfile
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from biocore_model import plugin_routes as routes
|
|
16
|
+
from biocore_model import ui_settings
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FakeCore:
|
|
20
|
+
def snapshot(self): return {"v": 0.0, "a": 0.4}
|
|
21
|
+
def intent(self): return {}
|
|
22
|
+
def load_state(self, state): return None
|
|
23
|
+
def autonomy_state(self, relationship=None, now=None, persist=True):
|
|
24
|
+
return {"enabled": True, "ready": False, "V": 0.71, "theta": 0.60}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FakePlugin(routes.RoutesMixin):
|
|
28
|
+
def __init__(self, data_dir, *, with_data_dir=True, state_attr="method",
|
|
29
|
+
state_raises=False):
|
|
30
|
+
self._tmp = data_dir
|
|
31
|
+
if with_data_dir:
|
|
32
|
+
self.data_dir = str(data_dir)
|
|
33
|
+
self._data_dir = data_dir
|
|
34
|
+
self.context = type("Ctx", (), {"persona_manager": None})()
|
|
35
|
+
self.cores = {} # empty cores
|
|
36
|
+
self.profile = {}
|
|
37
|
+
self.persona_text = ""
|
|
38
|
+
self.self_model = {}
|
|
39
|
+
self.enabled = True
|
|
40
|
+
self.inject_intent = True
|
|
41
|
+
self.proactive_reply = False
|
|
42
|
+
self.proactive_autonomy = False
|
|
43
|
+
self.proactive_send = False
|
|
44
|
+
self.memory_dedup_embedding = True
|
|
45
|
+
self._migration_note = ""
|
|
46
|
+
if state_attr == "method":
|
|
47
|
+
if state_raises:
|
|
48
|
+
def _state():
|
|
49
|
+
raise RuntimeError("state source down")
|
|
50
|
+
else:
|
|
51
|
+
def _state():
|
|
52
|
+
return {"source": "method"}
|
|
53
|
+
self._autonomy_readonly_state = _state
|
|
54
|
+
self._relationship_of = lambda data=None: {}
|
|
55
|
+
self._working_set_preview = lambda key: {}
|
|
56
|
+
self._ensure_autonomy_task = lambda: None
|
|
57
|
+
self._functions_public = lambda: {}
|
|
58
|
+
self._llm_provider_options = lambda: []
|
|
59
|
+
self._public_api_config = lambda: {}
|
|
60
|
+
self._memory_stats = lambda: {}
|
|
61
|
+
self._decayed_merge_count = lambda *a, **k: 0
|
|
62
|
+
self._event_archive = lambda *a, **k: []
|
|
63
|
+
self._insights = lambda *a, **k: []
|
|
64
|
+
self._astrbot_llm_default = lambda: None
|
|
65
|
+
self._astrbot_embedding_default = lambda: None
|
|
66
|
+
self._astrbot_rerank_default = lambda: None
|
|
67
|
+
self._astrbot_tts_default = lambda: None
|
|
68
|
+
self._astrbot_image_default = lambda: None
|
|
69
|
+
async def _kb_available(): return False
|
|
70
|
+
self._kb_available = _kb_available
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PageStatusAutonomyTest(unittest.TestCase):
|
|
74
|
+
def setUp(self):
|
|
75
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
76
|
+
self.tmp = Path(self._tmp.name)
|
|
77
|
+
self.authority = self.tmp / "plugin_data" / "astrbot_plugin_biocore"
|
|
78
|
+
self.authority.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
(self.authority / "ui_settings.json").write_text(json.dumps({
|
|
80
|
+
"autonomy": {"accumulator": {"V": 0.2838, "theta": 0.55,
|
|
81
|
+
"ticks_since_fire": 3}},
|
|
82
|
+
"schema": 1}, ensure_ascii=False), encoding="utf-8")
|
|
83
|
+
routes.DATA_DIR = self.authority
|
|
84
|
+
routes.MEMORY_DIR = self.tmp / "memory"
|
|
85
|
+
routes.STATE_DIR = self.tmp / "states"
|
|
86
|
+
routes.USER_DIR = self.tmp / "users"
|
|
87
|
+
routes.DEFAULT_PROFILE = {}
|
|
88
|
+
routes.DEFAULT_MEMORY_TRACE = {}
|
|
89
|
+
routes._memory_index_rows = lambda *a, **k: []
|
|
90
|
+
routes._relationship_rows = lambda *a, **k: []
|
|
91
|
+
routes.LLM_FUNCTIONS = ()
|
|
92
|
+
routes.LLM_FUNCTION_DEFAULTS = {}
|
|
93
|
+
for folder in (routes.MEMORY_DIR, routes.STATE_DIR, routes.USER_DIR):
|
|
94
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
# isolate from the real instance layout for attr-based tests
|
|
96
|
+
self._orig_module_file = routes._MODULE_FILE
|
|
97
|
+
routes._MODULE_FILE = (self.tmp / "no_instance" / "plugins" / "x"
|
|
98
|
+
/ "biocore_model" / "plugin_routes.py")
|
|
99
|
+
|
|
100
|
+
def tearDown(self):
|
|
101
|
+
routes._MODULE_FILE = self._orig_module_file
|
|
102
|
+
self._tmp.cleanup()
|
|
103
|
+
|
|
104
|
+
def _run(self, plugin):
|
|
105
|
+
out = asyncio.run(plugin.page_status())
|
|
106
|
+
self.assertEqual(out.get("status"), "ok")
|
|
107
|
+
return out["data"]["autonomy"]
|
|
108
|
+
|
|
109
|
+
def test_data_dir_set_and_state_attr_is_method(self):
|
|
110
|
+
plugin = FakePlugin(self.authority, with_data_dir=True, state_attr="method")
|
|
111
|
+
path = routes._authority_settings_path(plugin)
|
|
112
|
+
self.assertEqual(Path(str(path)), self.authority / "ui_settings.json")
|
|
113
|
+
automation = self._run(plugin)
|
|
114
|
+
self.assertEqual(set(automation), {"enabled", "send", "state", "_debug"})
|
|
115
|
+
state = automation["state"]
|
|
116
|
+
self.assertEqual(state["V"], 0.2838)
|
|
117
|
+
self.assertEqual(state["theta"], 0.55)
|
|
118
|
+
self.assertEqual(state["ticks_since_fire"], 3)
|
|
119
|
+
|
|
120
|
+
def test_data_dir_missing_falls_back_to_authority(self):
|
|
121
|
+
plugin = FakePlugin(self.authority, with_data_dir=False, state_attr="method")
|
|
122
|
+
path = routes._authority_settings_path(plugin)
|
|
123
|
+
self.assertEqual(Path(str(path)), self.authority / "ui_settings.json")
|
|
124
|
+
state = self._run(plugin)["state"]
|
|
125
|
+
self.assertEqual(state["V"], 0.2838)
|
|
126
|
+
self.assertEqual(state["theta"], 0.55)
|
|
127
|
+
|
|
128
|
+
def test_stale_data_dir_reads_injected_authority(self):
|
|
129
|
+
stale = self.tmp / "stale_A"
|
|
130
|
+
stale.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
(stale / "ui_settings.json").write_text(json.dumps({
|
|
132
|
+
"autonomy": {"accumulator": {"V": None, "theta": None,
|
|
133
|
+
"ticks_since_fire": None}}},
|
|
134
|
+
ensure_ascii=False), encoding="utf-8")
|
|
135
|
+
plugin = FakePlugin(self.authority, with_data_dir=False, state_attr="method")
|
|
136
|
+
plugin.data_dir = str(stale) # stale data_dir must not win
|
|
137
|
+
state = self._run(plugin)["state"]
|
|
138
|
+
self.assertEqual(state["V"], 0.2838)
|
|
139
|
+
self.assertEqual(state["theta"], 0.55)
|
|
140
|
+
self.assertIsNotNone(state["ticks_since_fire"])
|
|
141
|
+
|
|
142
|
+
def test_state_source_failure_still_merges_accumulator(self):
|
|
143
|
+
plugin = FakePlugin(self.authority, with_data_dir=False, state_attr="method",
|
|
144
|
+
state_raises=True)
|
|
145
|
+
state = self._run(plugin)["state"]
|
|
146
|
+
self.assertEqual(state["V"], 0.2838)
|
|
147
|
+
self.assertEqual(state["theta"], 0.55)
|
|
148
|
+
|
|
149
|
+
def test_authority_file_survives_old_accumulator_none(self):
|
|
150
|
+
old = self.tmp / "old_A"
|
|
151
|
+
old.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
(old / "ui_settings.json").write_text(json.dumps({
|
|
153
|
+
"autonomy": {"accumulator": {"V": None, "theta": None,
|
|
154
|
+
"ticks_since_fire": None}}},
|
|
155
|
+
ensure_ascii=False), encoding="utf-8")
|
|
156
|
+
plugin = FakePlugin(self.authority, with_data_dir=False, state_attr="method")
|
|
157
|
+
state = self._run(plugin)["state"]
|
|
158
|
+
self.assertEqual(state["V"], 0.2838)
|
|
159
|
+
self.assertIsNotNone(state["theta"])
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_module_candidates_follow_instance_layout(self):
|
|
163
|
+
data_root = self.tmp / "inst" / "core" / "data"
|
|
164
|
+
mod_file = (data_root / "plugins" / "astrbot_plugin_biocore_standalone"
|
|
165
|
+
/ "biocore_model" / "plugin_routes.py")
|
|
166
|
+
routes._MODULE_FILE = mod_file
|
|
167
|
+
candidates = routes._module_candidate_paths()
|
|
168
|
+
self.assertEqual(Path(str(candidates[0])).resolve(),
|
|
169
|
+
(data_root / "plugin_data" / "astrbot_plugin_biocore"
|
|
170
|
+
/ "ui_settings.json").resolve())
|
|
171
|
+
self.assertEqual(Path(str(candidates[1])).resolve(),
|
|
172
|
+
(data_root / "plugins" / "astrbot_plugin_biocore_standalone"
|
|
173
|
+
/ "data" / "ui_settings.json").resolve())
|
|
174
|
+
|
|
175
|
+
def test_real_instance_authority_file(self):
|
|
176
|
+
authority = None
|
|
177
|
+
base = Path.home() / ".astrbot_launcher" / "instances"
|
|
178
|
+
for candidate in sorted(base.glob(
|
|
179
|
+
"*/core/data/plugin_data/astrbot_plugin_biocore/ui_settings.json")):
|
|
180
|
+
if candidate.is_file():
|
|
181
|
+
authority = candidate
|
|
182
|
+
break
|
|
183
|
+
if authority is None:
|
|
184
|
+
self.skipTest("no real AstrBot instance ui_settings.json")
|
|
185
|
+
data_root = authority.parents[2] # .../core/data
|
|
186
|
+
routes._MODULE_FILE = (data_root / "plugins" / "astrbot_plugin_biocore_standalone"
|
|
187
|
+
/ "biocore_model" / "plugin_routes.py")
|
|
188
|
+
candidates = routes._module_candidate_paths()
|
|
189
|
+
self.assertEqual(candidates[0], authority)
|
|
190
|
+
self.assertTrue(candidates[0].exists())
|
|
191
|
+
acc = routes._read_accumulator(candidates[0])
|
|
192
|
+
self.assertTrue(acc)
|
|
193
|
+
self.assertTrue(any(value is not None for value in acc.values()))
|
|
194
|
+
|
|
195
|
+
def test_debug_field_reports_used_candidate(self):
|
|
196
|
+
data_root = self.tmp / "inst_debug" / "core" / "data"
|
|
197
|
+
authority = data_root / "plugin_data" / "astrbot_plugin_biocore"
|
|
198
|
+
authority.mkdir(parents=True, exist_ok=True)
|
|
199
|
+
(authority / "ui_settings.json").write_text(json.dumps({
|
|
200
|
+
"autonomy": {"accumulator": {"V": 0.2838, "theta": 0.55}},
|
|
201
|
+
"schema": 1}, ensure_ascii=False), encoding="utf-8")
|
|
202
|
+
routes._MODULE_FILE = (data_root / "plugins" / "x"
|
|
203
|
+
/ "biocore_model" / "plugin_routes.py")
|
|
204
|
+
plugin = FakePlugin(self.authority, with_data_dir=False, state_attr="method")
|
|
205
|
+
automation = self._run(plugin)
|
|
206
|
+
self.assertEqual(automation["state"]["V"], 0.2838)
|
|
207
|
+
debug = automation["_debug"]
|
|
208
|
+
self.assertEqual(debug["used"], debug["candidates"][0])
|
|
209
|
+
self.assertTrue(debug["exists"][0])
|
|
210
|
+
self.assertIn("V", debug["acc_keys"])
|
|
211
|
+
self.assertIn("theta", debug["acc_keys"])
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
unittest.main()
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import unittest
|
|
3
|
+
from biocore_model import pipeline as pl
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def stage(name, **kw):
|
|
7
|
+
kw.setdefault("run", lambda ctx: {name: True})
|
|
8
|
+
return pl.Stage(name, **kw)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PipelineTest(unittest.TestCase):
|
|
12
|
+
def test_order(self):
|
|
13
|
+
order = []
|
|
14
|
+
p = pl.Pipeline([stage("a", run=lambda c: order.append("a") or {}), stage("b", run=lambda c: order.append("b") or {})])
|
|
15
|
+
p.run({})
|
|
16
|
+
self.assertEqual(order, ["a", "b"])
|
|
17
|
+
def test_skip_disabled(self):
|
|
18
|
+
p = pl.Pipeline([stage("a"), stage("b")])
|
|
19
|
+
p.set_enabled("a", False)
|
|
20
|
+
ctx = p.run({})
|
|
21
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "skipped")
|
|
22
|
+
def test_required_cannot_disable(self):
|
|
23
|
+
p = pl.Pipeline([stage("perceive")])
|
|
24
|
+
with self.assertRaises(ValueError):
|
|
25
|
+
p.set_enabled("perceive", False)
|
|
26
|
+
def test_exception_continues(self):
|
|
27
|
+
p = pl.Pipeline([stage("a", run=lambda c: 1 / 0), stage("b")])
|
|
28
|
+
ctx = p.run({})
|
|
29
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "failed")
|
|
30
|
+
self.assertEqual(ctx["trace"]["stages"][1]["status"], "ok")
|
|
31
|
+
def test_required_failure_aborts(self):
|
|
32
|
+
p = pl.Pipeline([stage("think", required=True, run=lambda c: 1 / 0)])
|
|
33
|
+
with self.assertRaises(pl.PipelineAbort):
|
|
34
|
+
p.run({})
|
|
35
|
+
def test_timeout_skipped(self):
|
|
36
|
+
import time
|
|
37
|
+
p = pl.Pipeline([stage("slow", timeout_ms=50, run=lambda c: time.sleep(0.2)), stage("b")])
|
|
38
|
+
ctx = p.run({})
|
|
39
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "timeout")
|
|
40
|
+
self.assertEqual(ctx["trace"]["stages"][1]["status"], "ok")
|
|
41
|
+
def test_required_timeout_aborts(self):
|
|
42
|
+
import time
|
|
43
|
+
p = pl.Pipeline([stage("express", timeout_ms=30, run=lambda c: time.sleep(0.15))])
|
|
44
|
+
with self.assertRaises(pl.PipelineAbort):
|
|
45
|
+
p.run({})
|
|
46
|
+
def test_on_error_abort(self):
|
|
47
|
+
p = pl.Pipeline([stage("a", on_error="abort", run=lambda c: 1 / 0)])
|
|
48
|
+
with self.assertRaises(pl.PipelineAbort):
|
|
49
|
+
p.run({})
|
|
50
|
+
def test_trace_fields(self):
|
|
51
|
+
p = pl.Pipeline([stage("a")])
|
|
52
|
+
ctx = p.run({})
|
|
53
|
+
record = ctx["trace"]["stages"][0]
|
|
54
|
+
for key in ("name", "ms", "status", "note"):
|
|
55
|
+
self.assertIn(key, record)
|
|
56
|
+
def test_ring_limit_50(self):
|
|
57
|
+
p = pl.Pipeline([stage("a")])
|
|
58
|
+
for _ in range(60):
|
|
59
|
+
p.run({})
|
|
60
|
+
self.assertEqual(len(p._ring), 50)
|
|
61
|
+
def test_get_status_fields(self):
|
|
62
|
+
p = pl.Pipeline([stage("a"), stage("b")])
|
|
63
|
+
status = p.get_status()
|
|
64
|
+
row = status["stages"][0]
|
|
65
|
+
for key in ("name", "required", "enabled", "last_status", "last_ms"):
|
|
66
|
+
self.assertIn(key, row)
|
|
67
|
+
def test_get_status_after_run(self):
|
|
68
|
+
p = pl.Pipeline([stage("a")])
|
|
69
|
+
p.run({})
|
|
70
|
+
status = p.get_status()
|
|
71
|
+
self.assertEqual(status["stages"][0]["last_status"], "ok")
|
|
72
|
+
self.assertIn("stages", status["last_trace"])
|
|
73
|
+
def test_each_non_required_disabled_still_valid(self):
|
|
74
|
+
names = ["a", "b", "c"]
|
|
75
|
+
for name in names:
|
|
76
|
+
p = pl.Pipeline([stage(n) for n in names])
|
|
77
|
+
p.set_enabled(name, False)
|
|
78
|
+
ctx = p.run({})
|
|
79
|
+
self.assertIn("trace", ctx)
|
|
80
|
+
def test_unknown_set_enabled(self):
|
|
81
|
+
p = pl.Pipeline([stage("a")])
|
|
82
|
+
with self.assertRaises(KeyError):
|
|
83
|
+
p.set_enabled("nope", False)
|
|
84
|
+
def test_ctx_updated_by_stage(self):
|
|
85
|
+
p = pl.Pipeline([stage("a", run=lambda c: {"value": 7})])
|
|
86
|
+
self.assertEqual(p.run({})["value"], 7)
|
|
87
|
+
def test_ctx_input_preserved(self):
|
|
88
|
+
p = pl.Pipeline([stage("a")])
|
|
89
|
+
ctx = p.run({"event": "m1"})
|
|
90
|
+
self.assertEqual(ctx["event"], "m1")
|
|
91
|
+
def test_default_stages_all_required(self):
|
|
92
|
+
stages = pl.default_stages()
|
|
93
|
+
self.assertEqual([s.name for s in stages], ["perceive", "think", "express", "present"])
|
|
94
|
+
self.assertTrue(all(s.required for s in stages))
|
|
95
|
+
def test_no_main_import(self):
|
|
96
|
+
import pathlib
|
|
97
|
+
source = pathlib.Path(__file__).resolve().parent.parent.joinpath("pipeline.py").read_text(encoding="utf-8")
|
|
98
|
+
self.assertNotIn("import main", source)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class FakeClock:
|
|
102
|
+
"""Injected clock: deterministic seconds, advanced explicitly by a stage."""
|
|
103
|
+
|
|
104
|
+
def __init__(self):
|
|
105
|
+
self.now = 0.0
|
|
106
|
+
|
|
107
|
+
def __call__(self):
|
|
108
|
+
return self.now
|
|
109
|
+
|
|
110
|
+
def advance(self, seconds):
|
|
111
|
+
self.now += float(seconds)
|
|
112
|
+
return self.now
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class PipelineContractTest(unittest.TestCase):
|
|
116
|
+
def test_ctx_contract_fields(self):
|
|
117
|
+
ctx = pl.make_ctx()
|
|
118
|
+
self.assertEqual(set(ctx.keys()), set(pl.CTX_FIELDS))
|
|
119
|
+
self.assertEqual(len(pl.CTX_FIELDS), 16)
|
|
120
|
+
self.assertEqual(ctx["trace"]["stages"], [])
|
|
121
|
+
|
|
122
|
+
def test_run_fills_all_ctx_fields(self):
|
|
123
|
+
p = pl.Pipeline([stage("a")])
|
|
124
|
+
ctx = p.run({"event": {"text": "hi"}})
|
|
125
|
+
for field in pl.CTX_FIELDS:
|
|
126
|
+
self.assertIn(field, ctx)
|
|
127
|
+
self.assertEqual(ctx["event"], {"text": "hi"})
|
|
128
|
+
|
|
129
|
+
def test_run_does_not_mutate_input(self):
|
|
130
|
+
p = pl.Pipeline([stage("a")])
|
|
131
|
+
given = {"event": "m1"}
|
|
132
|
+
p.run(given)
|
|
133
|
+
self.assertEqual(given, {"event": "m1"})
|
|
134
|
+
|
|
135
|
+
def test_register_appends(self):
|
|
136
|
+
p = pl.Pipeline([stage("a")])
|
|
137
|
+
p.register(stage("b"))
|
|
138
|
+
self.assertEqual(p.names(), ["a", "b"])
|
|
139
|
+
|
|
140
|
+
def test_register_accepts_dict(self):
|
|
141
|
+
p = pl.Pipeline()
|
|
142
|
+
p.register({"name": "a", "run": lambda ctx: None})
|
|
143
|
+
self.assertEqual(p.names(), ["a"])
|
|
144
|
+
|
|
145
|
+
def test_register_duplicate_raises(self):
|
|
146
|
+
p = pl.Pipeline([stage("a")])
|
|
147
|
+
with self.assertRaises(ValueError):
|
|
148
|
+
p.register(stage("a"))
|
|
149
|
+
|
|
150
|
+
def test_register_bad_type(self):
|
|
151
|
+
p = pl.Pipeline()
|
|
152
|
+
with self.assertRaises(TypeError):
|
|
153
|
+
p.register("nope")
|
|
154
|
+
|
|
155
|
+
def test_stage_rejects_empty_name(self):
|
|
156
|
+
with self.assertRaises(ValueError):
|
|
157
|
+
pl.Stage("", lambda ctx: None)
|
|
158
|
+
|
|
159
|
+
def test_stage_rejects_non_callable_run(self):
|
|
160
|
+
with self.assertRaises(TypeError):
|
|
161
|
+
pl.Stage("a", "not-callable")
|
|
162
|
+
|
|
163
|
+
def test_stage_rejects_negative_timeout(self):
|
|
164
|
+
with self.assertRaises(ValueError):
|
|
165
|
+
pl.Stage("a", lambda ctx: None, timeout_ms=-1)
|
|
166
|
+
|
|
167
|
+
def test_invalid_on_error_normalized(self):
|
|
168
|
+
st = pl.Stage("a", lambda ctx: None, on_error="explode")
|
|
169
|
+
self.assertEqual(st.on_error, "skip")
|
|
170
|
+
|
|
171
|
+
def test_start_by_name(self):
|
|
172
|
+
order = []
|
|
173
|
+
p = pl.Pipeline([stage("a", run=lambda c: order.append("a") or {}),
|
|
174
|
+
stage("b", run=lambda c: order.append("b") or {}),
|
|
175
|
+
stage("c", run=lambda c: order.append("c") or {})])
|
|
176
|
+
ctx = p.run({}, start="b")
|
|
177
|
+
self.assertEqual(order, ["b", "c"])
|
|
178
|
+
self.assertEqual([row["status"] for row in ctx["trace"]["stages"]],
|
|
179
|
+
["skipped", "ok", "ok"])
|
|
180
|
+
self.assertEqual(ctx["trace"]["stages"][0]["note"], "before_start")
|
|
181
|
+
|
|
182
|
+
def test_start_by_index(self):
|
|
183
|
+
order = []
|
|
184
|
+
p = pl.Pipeline([stage("a", run=lambda c: order.append("a") or {}),
|
|
185
|
+
stage("b", run=lambda c: order.append("b") or {})])
|
|
186
|
+
p.run({}, start=1)
|
|
187
|
+
self.assertEqual(order, ["b"])
|
|
188
|
+
|
|
189
|
+
def test_start_unknown_raises(self):
|
|
190
|
+
p = pl.Pipeline([stage("a")])
|
|
191
|
+
with self.assertRaises(KeyError):
|
|
192
|
+
p.run({}, start="nope")
|
|
193
|
+
|
|
194
|
+
def test_start_out_of_range(self):
|
|
195
|
+
p = pl.Pipeline([stage("a")])
|
|
196
|
+
with self.assertRaises(IndexError):
|
|
197
|
+
p.run({}, start=5)
|
|
198
|
+
|
|
199
|
+
def test_status_enabled_reflects_toggle(self):
|
|
200
|
+
p = pl.Pipeline([stage("a"), stage("b")])
|
|
201
|
+
p.set_enabled("a", False)
|
|
202
|
+
rows = {row["name"]: row for row in p.get_status()["stages"]}
|
|
203
|
+
self.assertFalse(rows["a"]["enabled"])
|
|
204
|
+
self.assertTrue(rows["b"]["enabled"])
|
|
205
|
+
p.set_enabled("a", True)
|
|
206
|
+
rows = {row["name"]: row for row in p.get_status()["stages"]}
|
|
207
|
+
self.assertTrue(rows["a"]["enabled"])
|
|
208
|
+
|
|
209
|
+
def test_all_required_stages_cannot_disable(self):
|
|
210
|
+
for name in pl.REQUIRED_STAGES:
|
|
211
|
+
p = pl.Pipeline([stage(name)])
|
|
212
|
+
with self.assertRaises(ValueError):
|
|
213
|
+
p.set_enabled(name, False)
|
|
214
|
+
self.assertTrue(p.set_enabled(name, True))
|
|
215
|
+
|
|
216
|
+
def test_fallback_used_on_error(self):
|
|
217
|
+
p = pl.Pipeline([
|
|
218
|
+
pl.Stage("a", lambda c: 1 / 0, on_error="fallback",
|
|
219
|
+
fallback=lambda c: {"reply": "fb"}),
|
|
220
|
+
stage("b")])
|
|
221
|
+
ctx = p.run({})
|
|
222
|
+
first = ctx["trace"]["stages"][0]
|
|
223
|
+
self.assertEqual(first["status"], "failed")
|
|
224
|
+
self.assertIn("fallback:ok", first["note"])
|
|
225
|
+
self.assertEqual(ctx["reply"], "fb")
|
|
226
|
+
self.assertEqual(ctx["trace"]["stages"][1]["status"], "ok")
|
|
227
|
+
|
|
228
|
+
def test_fallback_failure_keeps_note(self):
|
|
229
|
+
def bad_fallback(ctx):
|
|
230
|
+
raise ValueError("boom")
|
|
231
|
+
p = pl.Pipeline([pl.Stage("a", lambda c: 1 / 0, on_error="fallback",
|
|
232
|
+
fallback=bad_fallback)])
|
|
233
|
+
ctx = p.run({})
|
|
234
|
+
self.assertIn("fallback:ValueError", ctx["trace"]["stages"][0]["note"])
|
|
235
|
+
|
|
236
|
+
def test_on_error_abort_non_required(self):
|
|
237
|
+
p = pl.Pipeline([pl.Stage("a", lambda c: 1 / 0, on_error="abort")])
|
|
238
|
+
with self.assertRaises(pl.PipelineAbort):
|
|
239
|
+
p.run({})
|
|
240
|
+
|
|
241
|
+
def test_clock_injected_ms_is_deterministic(self):
|
|
242
|
+
clock = FakeClock()
|
|
243
|
+
|
|
244
|
+
def run(ctx):
|
|
245
|
+
clock.advance(0.012)
|
|
246
|
+
return {}
|
|
247
|
+
|
|
248
|
+
p = pl.Pipeline([pl.Stage("a", run, timeout_ms=1000)], clock=clock)
|
|
249
|
+
ctx = p.run({})
|
|
250
|
+
self.assertEqual(ctx["trace"]["stages"][0]["ms"], 12.0)
|
|
251
|
+
self.assertEqual(p.get_status()["stages"][0]["last_ms"], 12.0)
|
|
252
|
+
|
|
253
|
+
def test_timeout_with_injected_clock(self):
|
|
254
|
+
clock = FakeClock()
|
|
255
|
+
|
|
256
|
+
def run(ctx):
|
|
257
|
+
clock.advance(0.2)
|
|
258
|
+
return {}
|
|
259
|
+
|
|
260
|
+
p = pl.Pipeline([pl.Stage("a", run, timeout_ms=50), stage("b")], clock=clock)
|
|
261
|
+
ctx = p.run({})
|
|
262
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "timeout")
|
|
263
|
+
self.assertEqual(ctx["trace"]["stages"][1]["status"], "ok")
|
|
264
|
+
|
|
265
|
+
def test_zero_timeout_never_times_out(self):
|
|
266
|
+
clock = FakeClock()
|
|
267
|
+
|
|
268
|
+
def run(ctx):
|
|
269
|
+
clock.advance(5.0)
|
|
270
|
+
return {}
|
|
271
|
+
|
|
272
|
+
p = pl.Pipeline([pl.Stage("a", run, timeout_ms=0)], clock=clock)
|
|
273
|
+
ctx = p.run({})
|
|
274
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "ok")
|
|
275
|
+
|
|
276
|
+
def test_explicit_timeout_error_maps_to_timeout(self):
|
|
277
|
+
def run(ctx):
|
|
278
|
+
raise TimeoutError("stage self-report")
|
|
279
|
+
|
|
280
|
+
p = pl.Pipeline([pl.Stage("a", run, timeout_ms=0)])
|
|
281
|
+
ctx = p.run({})
|
|
282
|
+
self.assertEqual(ctx["trace"]["stages"][0]["status"], "timeout")
|
|
283
|
+
|
|
284
|
+
def test_ring_keeps_last_50_in_order(self):
|
|
285
|
+
p = pl.Pipeline([stage("a"), stage("b")])
|
|
286
|
+
for _ in range(30):
|
|
287
|
+
p.run({})
|
|
288
|
+
self.assertEqual(len(p._ring), 50)
|
|
289
|
+
self.assertEqual(p._ring[-1]["name"], "b")
|
|
290
|
+
self.assertEqual(p._ring[-2]["name"], "a")
|
|
291
|
+
|
|
292
|
+
def test_abort_attaches_trace_and_updates_status(self):
|
|
293
|
+
p = pl.Pipeline([pl.Stage("perceive", lambda c: 1 / 0), stage("b")])
|
|
294
|
+
try:
|
|
295
|
+
p.run({})
|
|
296
|
+
except pl.PipelineAbort as exc:
|
|
297
|
+
self.assertEqual(exc.stage_name, "perceive")
|
|
298
|
+
self.assertEqual(exc.trace["stages"][0]["status"], "failed")
|
|
299
|
+
else:
|
|
300
|
+
self.fail("PipelineAbort not raised")
|
|
301
|
+
status = p.get_status()
|
|
302
|
+
self.assertEqual(status["stages"][0]["last_status"], "failed")
|
|
303
|
+
self.assertEqual(status["last_trace"]["stages"][0]["status"], "failed")
|
|
304
|
+
|
|
305
|
+
def test_disable_each_non_required_stage_still_valid(self):
|
|
306
|
+
for name in ("a", "b", "c"):
|
|
307
|
+
p = pl.Pipeline([stage("a"), stage("b"), stage("c")])
|
|
308
|
+
p.set_enabled(name, False)
|
|
309
|
+
ctx = p.run({})
|
|
310
|
+
rows = {row["name"]: row for row in ctx["trace"]["stages"]}
|
|
311
|
+
self.assertEqual(rows[name]["status"], "skipped")
|
|
312
|
+
self.assertEqual(rows[name]["note"], "disabled")
|
|
313
|
+
self.assertEqual(sum(1 for row in rows.values() if row["status"] == "ok"), 2)
|
|
314
|
+
for field in pl.CTX_FIELDS:
|
|
315
|
+
self.assertIn(field, ctx)
|
|
316
|
+
|
|
317
|
+
def test_skip_note_and_ms(self):
|
|
318
|
+
p = pl.Pipeline([stage("a")])
|
|
319
|
+
p.set_enabled("a", False)
|
|
320
|
+
ctx = p.run({})
|
|
321
|
+
row = ctx["trace"]["stages"][0]
|
|
322
|
+
self.assertEqual(row["ms"], 0.0)
|
|
323
|
+
self.assertEqual(p.get_status()["stages"][0]["last_status"], "skipped")
|
|
324
|
+
|
|
325
|
+
def test_default_stages_run_produces_reply(self):
|
|
326
|
+
p = pl.Pipeline(pl.default_stages())
|
|
327
|
+
ctx = p.run(pl.make_ctx(event={"text": "hi"}))
|
|
328
|
+
self.assertTrue(ctx["reply"])
|
|
329
|
+
self.assertEqual([row["status"] for row in ctx["trace"]["stages"]],
|
|
330
|
+
["ok", "ok", "ok", "ok"])
|
|
331
|
+
|
|
332
|
+
def test_status_fields_and_trace_contract(self):
|
|
333
|
+
p = pl.Pipeline([stage("a")])
|
|
334
|
+
ctx = p.run({})
|
|
335
|
+
row = ctx["trace"]["stages"][0]
|
|
336
|
+
self.assertEqual(set(row.keys()), {"name", "ms", "status", "note"})
|
|
337
|
+
status = p.get_status()
|
|
338
|
+
self.assertEqual(set(status.keys()), {"stages", "last_trace"})
|
|
339
|
+
self.assertEqual(set(status["stages"][0].keys()),
|
|
340
|
+
{"name", "required", "enabled", "last_status", "last_ms"})
|
|
341
|
+
self.assertIn("ring", status["last_trace"])
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
if __name__ == "__main__":
|
|
345
|
+
unittest.main()
|