192 lines
5.7 KiB
Python
192 lines
5.7 KiB
Python
import asyncio
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from thinkstorm import database
|
|
from thinkstorm import auth
|
|
from thinkstorm.api import ideas as ideas_api
|
|
from thinkstorm.models import User, UserRole
|
|
from thinkstorm.services import gitea as gitea_module
|
|
|
|
|
|
@pytest.fixture
|
|
def isolated_db(tmp_path, monkeypatch):
|
|
monkeypatch.setattr(database, "DB_PATH", str(tmp_path / "claimant-gitea.db"))
|
|
database.init_db()
|
|
return tmp_path
|
|
|
|
|
|
def _insert_idea(idea_id, claimed_by=None):
|
|
now = database.get_utc_now()
|
|
with database.get_db() as conn:
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO ideas
|
|
(id, original_text, submitted_at, title, summary, lifecycle_state,
|
|
processing_state, claimed_by, claimed_at, created_at, updated_at)
|
|
VALUES (?, 'Original idea', ?, 'Claimant Publishing', 'Summary', ?,
|
|
'IDLE', ?, ?, ?, ?)
|
|
""",
|
|
(
|
|
idea_id,
|
|
now,
|
|
"CLAIMED" if claimed_by else "AVAILABLE",
|
|
claimed_by,
|
|
now if claimed_by else None,
|
|
now,
|
|
now,
|
|
),
|
|
)
|
|
|
|
|
|
def _user(username, role=UserRole.USER):
|
|
return User(id=10, username=username, password_hash="", role=role)
|
|
|
|
|
|
def test_unclaimed_idea_cannot_be_published(isolated_db):
|
|
_insert_idea("TS-PUB1")
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(
|
|
ideas_api.sync_idea_to_gitea(
|
|
"TS-PUB1", current_user=_user("researcher")
|
|
)
|
|
)
|
|
|
|
assert exc.value.status_code == 409
|
|
|
|
|
|
def test_claimant_must_have_verified_gitea_identity(isolated_db):
|
|
_insert_idea("TS-PUB2", claimed_by="researcher")
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(
|
|
ideas_api.sync_idea_to_gitea(
|
|
"TS-PUB2", current_user=_user("researcher")
|
|
)
|
|
)
|
|
|
|
assert exc.value.status_code == 403
|
|
assert "sign in with Gitea" in exc.value.detail
|
|
|
|
|
|
def test_only_claimant_can_publish(isolated_db):
|
|
_insert_idea("TS-PUB3", claimed_by="researcher")
|
|
with database.get_db() as conn:
|
|
conn.execute(
|
|
"UPDATE users SET gitea_id = 1234 WHERE username = 'researcher'"
|
|
)
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
asyncio.run(
|
|
ideas_api.sync_idea_to_gitea(
|
|
"TS-PUB3", current_user=_user("someone-else")
|
|
)
|
|
)
|
|
|
|
assert exc.value.status_code == 403
|
|
|
|
|
|
def test_claimant_publish_is_idempotent_and_grants_write_access(
|
|
isolated_db, monkeypatch
|
|
):
|
|
_insert_idea("TS-PUB4", claimed_by="researcher")
|
|
with database.get_db() as conn:
|
|
conn.execute(
|
|
"UPDATE users SET gitea_id = 1234 WHERE username = 'researcher'"
|
|
)
|
|
|
|
calls = []
|
|
|
|
async def fake_publish(**kwargs):
|
|
calls.append(kwargs)
|
|
return {
|
|
"repo_name": "thinkstorm/ts-pub4",
|
|
"repo_url": "https://git.example/thinkstorm/ts-pub4",
|
|
"clone_url": "https://git.example/thinkstorm/ts-pub4.git",
|
|
"ssh_url": "ssh://[email protected]/thinkstorm/ts-pub4.git",
|
|
"local_path": str(isolated_db / "TS-PUB4"),
|
|
"published": True,
|
|
}
|
|
|
|
monkeypatch.setattr(
|
|
ideas_api.gitea_svc, "persist_idea_dossier_repo", fake_publish
|
|
)
|
|
|
|
first = asyncio.run(
|
|
ideas_api.sync_idea_to_gitea(
|
|
"TS-PUB4", current_user=_user("researcher")
|
|
)
|
|
)
|
|
second = asyncio.run(
|
|
ideas_api.sync_idea_to_gitea(
|
|
"TS-PUB4", current_user=_user("researcher")
|
|
)
|
|
)
|
|
|
|
assert first["gitea_repo_name"] == "thinkstorm/ts-pub4"
|
|
assert second["gitea_repo_url"].endswith("/ts-pub4")
|
|
assert all(call["publish_remote"] is True for call in calls)
|
|
assert all(call["collaborator_username"] == "researcher" for call in calls)
|
|
|
|
with database.get_db() as conn:
|
|
idea = conn.execute(
|
|
"SELECT gitea_repo_name, gitea_repo_url FROM ideas WHERE id = 'TS-PUB4'"
|
|
).fetchone()
|
|
resources = conn.execute(
|
|
"""
|
|
SELECT COUNT(*) AS count FROM external_resources
|
|
WHERE idea_id = 'TS-PUB4' AND resource_type = 'GITEA_DOSSIER'
|
|
"""
|
|
).fetchone()["count"]
|
|
|
|
assert idea["gitea_repo_name"] == "thinkstorm/ts-pub4"
|
|
assert resources == 1
|
|
|
|
|
|
def test_oauth_links_existing_local_account_to_gitea(isolated_db):
|
|
linked_user = auth.get_or_create_gitea_user(
|
|
{"login": "researcher", "id": 9876, "is_admin": False}
|
|
)
|
|
|
|
with database.get_db() as conn:
|
|
row = conn.execute(
|
|
"SELECT gitea_id FROM users WHERE username = 'researcher'"
|
|
).fetchone()
|
|
|
|
assert linked_user.username == "researcher"
|
|
assert row["gitea_id"] == 9876
|
|
|
|
|
|
def test_local_only_dossier_persistence_never_contacts_gitea(
|
|
isolated_db, monkeypatch
|
|
):
|
|
artifact_root = isolated_db / "artifacts"
|
|
monkeypatch.setattr(gitea_module, "ARTIFACTS_DIR", artifact_root)
|
|
adapter = gitea_module.GiteaAdapter(api_token="configured-token")
|
|
|
|
def unexpected_remote_call(*_args, **_kwargs):
|
|
raise AssertionError("local persistence attempted a remote Gitea call")
|
|
|
|
monkeypatch.setattr(adapter, "_ensure_repo", unexpected_remote_call)
|
|
|
|
result = asyncio.run(
|
|
adapter.persist_idea_dossier_repo(
|
|
idea_id="TS-LOCAL",
|
|
title="Local only",
|
|
summary="Summary",
|
|
original_text="Original",
|
|
categories=[],
|
|
tags=[],
|
|
lifecycle_state="AVAILABLE",
|
|
research_docs={"analysis.md": "Research"},
|
|
outputs={},
|
|
provenance_runs=[],
|
|
publish_remote=False,
|
|
)
|
|
)
|
|
|
|
assert result["published"] is False
|
|
assert (artifact_root / "TS-LOCAL" / "README.md").exists()
|