mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-24 12:54:48 -06:00
fb190f8977
* Normalize session_id into ws_id as sole persistent identity Eliminate the separate session_id concept. The workstream ID (ws_id) is now the single identity used for both real-time routing and conversation persistence, removing a layer of indirection that was 1:1 in practice and buggy on resume (stale pointers, orphaned rows). Schema changes (migration 006): - Drop sessions table; add alias/title columns to workstreams - Rename conversations.session_id → ws_id - Rename session_config table → workstream_config (ws_id column) - Data migration remaps existing conversations to ws_id Storage/API renames: - register_session → register_workstream (already existed, merged) - save_message/load_messages now keyed by ws_id - resolve_session → resolve_workstream - ChatSession.session_id property → ws_id - ChatSession.resume_session() → resume() - resume_session field → resume_ws - SessionResumedEvent → WorkstreamResumedEvent - /api/sessions → /api/workstreams/saved - /sessions slash command → /workstreams - --session-retention-days → --retention-days Channel eviction recovery simplified: reuses old ws_id directly instead of get_session_id_by_ws() reverse lookup. * Fix Copilot review feedback: stale session wording in docs, regenerate OpenAPI spec - docs/channels.md: "resumes the session" → "resumes the workstream", "Session resumed:" → "Resumed:", "old session was pruned" → "old workstream was pruned" - docs/api-reference.md: "Each session object" → "Each saved workstream object", field descriptions updated, removed stale node_id field - sdk/typescript/openapi-server.json: fully regenerated from Python models — removes all stale session_id properties from WorkstreamInfo, DashboardWorkstream, CreateWorkstreamResponse schemas
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
"""Tests for the atomic workstream resumption flow.
|
|
|
|
Covers CreateWorkstreamMessage resume_ws field, WorkstreamResumedEvent,
|
|
WorkstreamCreatedEvent resumed fields, and server endpoint handling.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from turnstone.mq.protocol import (
|
|
CreateWorkstreamMessage,
|
|
WorkstreamCreatedEvent,
|
|
WorkstreamResumedEvent,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Protocol tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCreateWorkstreamMessageResumeField:
|
|
def test_resume_ws_defaults_empty(self) -> None:
|
|
msg = CreateWorkstreamMessage(name="test")
|
|
assert msg.resume_ws == ""
|
|
|
|
def test_resume_ws_set(self) -> None:
|
|
msg = CreateWorkstreamMessage(name="test", resume_ws="ws-abc")
|
|
assert msg.resume_ws == "ws-abc"
|
|
|
|
def test_resume_ws_serializes(self) -> None:
|
|
msg = CreateWorkstreamMessage(resume_ws="ws-xyz")
|
|
data = json.loads(msg.to_json())
|
|
assert data["resume_ws"] == "ws-xyz"
|
|
|
|
def test_resume_ws_deserializes(self) -> None:
|
|
msg = CreateWorkstreamMessage(resume_ws="ws-123")
|
|
raw = msg.to_json()
|
|
from turnstone.mq.protocol import InboundMessage
|
|
|
|
restored = InboundMessage.from_json(raw)
|
|
assert getattr(restored, "resume_ws", "") == "ws-123"
|
|
|
|
|
|
class TestWorkstreamCreatedEventResumeFields:
|
|
def test_default_not_resumed(self) -> None:
|
|
event = WorkstreamCreatedEvent(ws_id="ws-1", name="test")
|
|
assert event.resumed is False
|
|
assert event.message_count == 0
|
|
|
|
def test_resumed_fields(self) -> None:
|
|
event = WorkstreamCreatedEvent(ws_id="ws-1", name="test", resumed=True, message_count=42)
|
|
assert event.resumed is True
|
|
assert event.message_count == 42
|
|
|
|
def test_serializes_resumed_fields(self) -> None:
|
|
event = WorkstreamCreatedEvent(ws_id="ws-1", resumed=True, message_count=10)
|
|
data = json.loads(event.to_json())
|
|
assert data["resumed"] is True
|
|
assert data["message_count"] == 10
|
|
|
|
def test_deserializes_resumed_fields(self) -> None:
|
|
event = WorkstreamCreatedEvent(ws_id="ws-1", resumed=True, message_count=5)
|
|
from turnstone.mq.protocol import OutboundEvent
|
|
|
|
restored = OutboundEvent.from_json(event.to_json())
|
|
assert isinstance(restored, WorkstreamCreatedEvent)
|
|
assert restored.resumed is True
|
|
assert restored.message_count == 5
|
|
|
|
|
|
class TestWorkstreamResumedEvent:
|
|
def test_defaults(self) -> None:
|
|
event = WorkstreamResumedEvent(ws_id="ws-1")
|
|
assert event.type == "ws_resumed"
|
|
assert event.message_count == 0
|
|
assert event.name == ""
|
|
|
|
def test_with_values(self) -> None:
|
|
event = WorkstreamResumedEvent(ws_id="ws-1", message_count=25, name="My Chat")
|
|
assert event.message_count == 25
|
|
assert event.name == "My Chat"
|
|
|
|
def test_round_trip(self) -> None:
|
|
event = WorkstreamResumedEvent(ws_id="ws-1", message_count=10, name="Chat")
|
|
from turnstone.mq.protocol import OutboundEvent
|
|
|
|
restored = OutboundEvent.from_json(event.to_json())
|
|
assert isinstance(restored, WorkstreamResumedEvent)
|
|
assert restored.message_count == 10
|
|
assert restored.name == "Chat"
|
|
|
|
def test_registered_in_outbound_registry(self) -> None:
|
|
from turnstone.mq.protocol import _OUTBOUND_REGISTRY
|
|
|
|
assert "ws_resumed" in _OUTBOUND_REGISTRY
|
|
assert _OUTBOUND_REGISTRY["ws_resumed"] is WorkstreamResumedEvent
|