Compare commits

..

2 Commits

Author SHA1 Message Date
Alexander Payne
207037eaf9 test(#200): add tests for Gitea issue integration
All checks were successful
Sanity Checks / sanity-test (pull_request) Successful in 9s
Smoke Test / smoke (pull_request) Successful in 19s
- Add TestIntakeGiteaIntegration with mocked API tests
- Verify create_intake_issue returns dict with 'number'
- Verify close_intake_issue returns dict with 'state'

Refs #200
2026-04-30 20:30:57 -04:00
Alexander Payne
3b60afc5c7 feat(#200): add Gitea issue integration to intake system
- Add create_intake_issue() to create tracking issues for new submissions
- Add close_intake_issue() to close issues after processing
- Update crisis/__init__.py exports

Refs #200
2026-04-30 20:30:14 -04:00
3 changed files with 171 additions and 6 deletions

View File

@@ -4,11 +4,12 @@ Crisis detection and response system for the-door.
Stands between a broken man and a machine that would tell him to die.
"""
from .detect import detect_crisis, CrisisDetectionResult, format_result, get_urgency_emoji
from .response import process_message, generate_response, CrisisResponse
from .gateway import check_crisis, get_system_prompt, format_gateway_response
from .session_tracker import CrisisSessionTracker, SessionState, check_crisis_with_session
from .metrics import CrisisMetrics, AggregateMetrics
from .intake import (
handle_intake_submission,
IntakeResult,
create_intake_issue,
close_intake_issue,
)
__all__ = [
"detect_crisis",
@@ -26,4 +27,8 @@ __all__ = [
"check_crisis_with_session",
"CrisisMetrics",
"AggregateMetrics",
"handle_intake_submission",
"IntakeResult",
"create_intake_issue",
"close_intake_issue",
]

View File

@@ -4,10 +4,15 @@ Intake submission handler for the-door.
Provides a lightweight function for receiving and processing test intake
submissions (QA/test user messages) with separate metrics tracking.
Also provides Gitea issue integration for tracking intake submissions
and their processing state.
Usage:
from crisis.intake import handle_intake_submission
from crisis.intake import handle_intake_submission, close_intake_issue
result = handle_intake_submission("This is a test message", test=True)
# Later, mark the intake issue as processed
close_intake_issue(200, "Processed and logged to test-intake metrics.")
"""
from dataclasses import dataclass
@@ -108,6 +113,125 @@ def _log_test_intake(detection, message: str) -> None:
f.write(json.dumps(record) + "\n")
def create_intake_issue(message: str, user: str = "Test User") -> dict:
"""
Create a Gitea issue to track an intake submission.
Args:
message: The user's message text.
user: The name to attribute the intake to.
Returns:
dict: The API response with the created issue (contains 'number').
Requires GITEA_TOKEN environment variable or ~/.config/gitea/token file.
"""
import os
import urllib.request
# Get token
token = os.environ.get("GITEA_TOKEN")
if not token:
token_file = os.path.expanduser("~/.config/gitea/token")
if os.path.exists(token_file):
with open(token_file) as f:
token = f.read().strip()
if not token:
raise ValueError("GITEA_TOKEN not set and ~/.config/gitea/token not found")
url = (
"https://forge.alexanderwhitestone.com/api/v1/"
"repos/Timmy_Foundation/the-door/issues"
)
body = f"**Message:** {message}"
data = json.dumps({
"title": f"Intake submission from {user}",
"body": body,
}).encode()
req = urllib.request.Request(
url,
data=data,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
return result
def close_intake_issue(issue_number: int, comment: str = "Processed.") -> dict:
"""
Close a Gitea intake issue and add a comment.
Args:
issue_number: The Gitea issue number (e.g., 200 for #200)
comment: Optional comment to add before closing.
Returns:
dict: The API response from closing the issue.
Requires GITEA_TOKEN environment variable or ~/.config/gitea/token file.
"""
import os
import urllib.request
import urllib.parse
# Get token
token = os.environ.get("GITEA_TOKEN")
if not token:
token_file = os.path.expanduser("~/.config/gitea/token")
if os.path.exists(token_file):
with open(token_file) as f:
token = f.read().strip()
if not token:
raise ValueError("GITEA_TOKEN not set and ~/.config/gitea/token not found")
# Add comment first
if comment:
comment_url = (
f"https://forge.alexanderwhitestone.com/api/v1/"
f"repos/Timmy_Foundation/the-door/issues/{issue_number}/comments"
)
comment_data = json.dumps({"body": comment}).encode()
req = urllib.request.Request(
comment_url,
data=comment_data,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
)
try:
with urllib.request.urlopen(req) as resp:
pass # Comment added
except Exception as e:
print(f"Warning: Failed to add comment: {e}")
# Close the issue
close_url = (
f"https://forge.alexanderwhitestone.com/api/v1/"
f"repos/Timmy_Foundation/the-door/issues/{issue_number}"
)
close_data = json.dumps({"state": "closed"}).encode()
req = urllib.request.Request(
close_url,
data=close_data,
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method="PATCH",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
return result
# ── Quick test interface ────────────────────────────────────────────
def _interactive():

View File

@@ -106,5 +106,41 @@ class TestIntakeIntegration(unittest.TestCase):
self.assertTrue(result.provide_988)
class TestIntakeGiteaIntegration(unittest.TestCase):
"""Tests for Gitea issue integration (requires token)."""
def test_create_intake_issue_structure(self):
"""create_intake_issue returns dict with 'number' key."""
# This is a unit test - we mock the API call
import unittest.mock as mock
import crisis.intake as intake_mod
mock_response = mock.MagicMock()
mock_response.read.return_value = b'{"number": 999, "title": "Test"}'
with mock.patch("urllib.request.urlopen", return_value=mock_response):
# Patch token check
original_dir = intake_mod._INTAKE_METRICS_DIR
try:
result = intake_mod.create_intake_issue("Test message", "Test User")
self.assertIn("number", result)
self.assertEqual(result["number"], 999)
finally:
pass
def test_close_intake_issue_structure(self):
"""close_intake_issue returns dict with 'state' key."""
import unittest.mock as mock
import crisis.intake as intake_mod
mock_response = mock.MagicMock()
mock_response.read.return_value = b'{"number": 200, "state": "closed"}'
with mock.patch("urllib.request.urlopen", return_value=mock_response):
result = intake_mod.close_intake_issue(200, "Processed.")
self.assertIn("state", result)
self.assertEqual(result["state"], "closed")
if __name__ == "__main__":
unittest.main()