""" test_canvas_service.py — Unit tests for CanvasService (demo mode in-memory store). """ import asyncio import pytest import sys import os # Ensure backend is on path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from oracle.canvas_service import CanvasService TENANT = "tenant_test_001" ACTOR = "user_test_001" def run(coro): return asyncio.get_event_loop().run_until_complete(coro) @pytest.fixture def svc(): """Fresh CanvasService instance with empty demo store per test.""" from oracle import canvas_service as _mod _mod._DEMO_PAGES.clear() _mod._DEMO_REVISIONS.clear() _mod._DEMO_COMPONENTS.clear() return CanvasService() def test_create_page(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR, title="Test Canvas")) assert page["tenantId"] == TENANT assert page["ownerId"] == ACTOR assert page["title"] == "Test Canvas" assert page["headRevision"] == 0 assert page["pageType"] == "main" assert page["branchName"] == "main" def test_get_page_not_found(svc): result = run(svc.get_page("nonexistent_id", TENANT)) assert result is None def test_get_page_returns_page(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) retrieved = run(svc.get_page(page["pageId"], TENANT)) assert retrieved is not None assert retrieved["pageId"] == page["pageId"] def test_commit_revision_advances_head(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) comps = [{"componentId": "cmp_001", "type": "barChart", "title": "Test"}] rev = run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary="Test prompt", components=comps, idempotency_key="ikey_001", )) assert rev["revisionNumber"] == 1 updated = run(svc.get_page(page["pageId"], TENANT)) assert updated["headRevision"] == 1 assert len(updated["components"]) == 1 def test_idempotency_key_prevents_double_commit(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) comps = [{"componentId": "cmp_001", "type": "barChart", "title": "Test"}] rev1 = run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary="First", components=comps, idempotency_key="ikey_idempotent", )) rev2 = run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary="Duplicate", components=comps, idempotency_key="ikey_idempotent", )) assert rev1["revisionId"] == rev2["revisionId"] # Head should still be 1 updated = run(svc.get_page(page["pageId"], TENANT)) assert updated["headRevision"] == 1 def test_rollback_creates_new_revision(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) comps_v1 = [{"componentId": "cmp_v1", "type": "barChart", "title": "V1"}] comps_v2 = [{"componentId": "cmp_v2", "type": "lineChart", "title": "V2"}] run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary="V1", components=comps_v1, idempotency_key="key_v1", )) run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary="V2", components=comps_v2, idempotency_key="key_v2", )) # Rollback to revision 1 rollback_rev = run(svc.rollback( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, target_revision=1, idempotency_key="key_rollback", )) assert rollback_rev["revisionNumber"] == 3 assert rollback_rev["commitKind"] == "rollback" revisions = run(svc.list_revisions(page["pageId"], TENANT)) assert len(revisions) == 3 # 3 revisions total def test_list_revisions_returns_newest_first(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) for i in range(3): run(svc.commit_revision( page_id=page["pageId"], tenant_id=TENANT, actor_id=ACTOR, commit_kind="prompt", commit_summary=f"Rev {i+1}", components=[], idempotency_key=f"key_{i}", )) revisions = run(svc.list_revisions(page["pageId"], TENANT)) assert revisions[0]["revisionNumber"] > revisions[-1]["revisionNumber"] def test_tenant_isolation(svc): page = run(svc.create_page(tenant_id=TENANT, owner_id=ACTOR)) # Different tenant cannot access the page result = run(svc.get_page(page["pageId"], "tenant_different_999")) assert result is None