import asyncio import pytest from fastapi import HTTPException from pydantic import ValidationError from investibook import config as cfg from investibook.api import jira_alerts as jira_api from investibook.api import sources as sources_api from investibook.api import workspaces as ws_api from investibook.db import queries as dbq from investibook.main import app from investibook.models.requests import CreateJiraAlertWorkspaceRequest @pytest.fixture def isolated_db(tmp_path, monkeypatch): db_path = tmp_path / "investibook_test.db" monkeypatch.setattr(dbq, "DB_PATH", db_path) dbq._db_init() return dbq @pytest.fixture def patched_workspace_io(tmp_path, monkeypatch): monkeypatch.setattr(ws_api, "WORKSPACES_DIR", tmp_path / "workspaces") monkeypatch.setattr(ws_api, "TEMPLATES_DIR", tmp_path / "templates") monkeypatch.setattr(ws_api, "_ensure_workspace_dir_initialized", lambda _wid: None) def test_jira_alert_request_normalizes_identity(): body = CreateJiraAlertWorkspaceRequest( jira_id=" o11y-12345 ", user_email=" OnCaller@PingCAP.com ", prompt=" diagnose this alert ", ) assert body.jira_id == "O11Y-12345" assert body.user_email == "oncaller@pingcap.com" assert body.prompt == "diagnose this alert" def test_jira_alert_ingestion_route_is_registered_as_async(): operation = app.openapi()["paths"]["/api/integrations/jira-alerts"]["post"] assert "202" in operation["responses"] @pytest.mark.parametrize("jira_id", ["ALT-123", "O11Y-a", "O11Y-1,O11Y-2"]) def test_jira_alert_request_rejects_non_o11y_ids(jira_id): with pytest.raises(ValidationError): CreateJiraAlertWorkspaceRequest( jira_id=jira_id, user_email="oncaller@pingcap.com", ) def test_jira_alert_ingestion_is_async_and_idempotent_per_user( isolated_db, patched_workspace_io, monkeypatch ): service = isolated_db._db_create_user("jira-ingest-service@pingcap.com", "pass-123") service.update(principal_type="service", service_scope="alert_ingest") sources_api._prepare_tasks.clear() async def _noop_prepare_task(*_args, **_kwargs): return None monkeypatch.setattr(sources_api, "_prepare_issue_source_task", _noop_prepare_task) monkeypatch.setattr(cfg, "INVESTIBOOK_WEB_BASE_URL", "https://dev.investibook.com") body = CreateJiraAlertWorkspaceRequest( jira_id="o11y-12345", user_email="OnCaller@PingCAP.com", ) first = asyncio.run(jira_api.ingest_jira_alert(body, principal=service)) repeated = asyncio.run(jira_api.ingest_jira_alert(body, principal=service)) assert first["status"] == "accepted" assert repeated["status"] == "existing" assert repeated["workspace_id"] == first["workspace_id"] assert first["workspace_url"] == ( f"https://dev.investibook.com/workspace/{first['workspace_id']}" ) owner = isolated_db._db_get_user_by_email("oncaller@pingcap.com") workspace = isolated_db._db_get_workspace(first["workspace_id"]) assert workspace["user_id"] == owner["id"] sources = isolated_db._db_get_blob("workspace_sources", first["workspace_id"]) assert len(sources) == 1 assert sources[0]["type"] == "jira_issue" assert sources[0]["issueId"] == "O11Y-12345" diagnostics = isolated_db._db_get_workspace_diagnostics(first["workspace_id"]) assert diagnostics["status"] == "pending" assert diagnostics["questions"] == [jira_api._default_analysis_prompt("O11Y-12345")] def test_same_jira_alert_can_create_one_workspace_for_each_user( isolated_db, patched_workspace_io, monkeypatch ): service = isolated_db._db_create_user("jira-ingest-service@pingcap.com", "pass-123") service.update(principal_type="service", service_scope="alert_ingest") sources_api._prepare_tasks.clear() async def _noop_prepare_task(*_args, **_kwargs): return None monkeypatch.setattr(sources_api, "_prepare_issue_source_task", _noop_prepare_task) first = asyncio.run( jira_api.ingest_jira_alert( CreateJiraAlertWorkspaceRequest( jira_id="O11Y-12345", user_email="first@pingcap.com" ), principal=service, ) ) second = asyncio.run( jira_api.ingest_jira_alert( CreateJiraAlertWorkspaceRequest( jira_id="O11Y-12345", user_email="second@pingcap.com" ), principal=service, ) ) assert first["workspace_id"] != second["workspace_id"] def test_concurrent_retries_return_one_workspace( isolated_db, patched_workspace_io, monkeypatch ): service = isolated_db._db_create_user("jira-ingest-service@pingcap.com", "pass-123") service.update(principal_type="service", service_scope="alert_ingest") sources_api._prepare_tasks.clear() async def _noop_prepare_task(*_args, **_kwargs): return None monkeypatch.setattr(sources_api, "_prepare_issue_source_task", _noop_prepare_task) body = CreateJiraAlertWorkspaceRequest( jira_id="O11Y-12345", user_email="oncaller@pingcap.com" ) async def _run_requests(): return await asyncio.gather( jira_api.ingest_jira_alert(body, principal=service), jira_api.ingest_jira_alert(body, principal=service), ) first, second = asyncio.run(_run_requests()) assert {first["status"], second["status"]} == {"accepted", "existing"} assert first["workspace_id"] == second["workspace_id"] def test_user_token_cannot_create_workspace_for_another_email(isolated_db): user = isolated_db._db_create_user("owner@pingcap.com", "pass-123") with pytest.raises(HTTPException) as exc_info: asyncio.run( jira_api.ingest_jira_alert( CreateJiraAlertWorkspaceRequest( jira_id="O11Y-12345", user_email="other@pingcap.com" ), principal=user, ) ) assert exc_info.value.status_code == 403