Files
Gitea-Tools/tests/test_webui_request_initiation.py
T
sysadminandClaude Opus 4.8 433f66add8 feat(webui): request preview, authorization, and workflow initiation (Closes #643)
Operators had to paste a role prompt into a terminal to start work, and
nothing enforced that the allocator had been consulted first, so two sessions
could reach for the same issue and each believe it was theirs. This adds a
request surface: a desired role, an issue or PR, and a stated intent, answered
by an authorization decision and - on confirmation - an exclusive assignment
from the allocator.

Preview (POST /api/v1/requests/preview, and the /requests form) runs five
checks and reports authorize/deny with a reason for each: console
authorization, capability resolution for the desired role, lease availability,
whether the allocator would independently select this work unit, and head
pinning for PR work. It is read-only - it calls the allocator with apply=false
and writes only an audit line. An unauthorized principal never reaches the
allocator or the control-plane DB, so a denial cannot enumerate the queue.

Initiation (POST /api/v1/requests/apply) never assigns the requested item
directly. It runs a dry-run first and proceeds only when the allocator would
independently pick that exact work unit, carrying the dry-run's
candidate_set_fingerprint as a CAS pin; otherwise it returns wait or blocked
and mutates nothing. An active claim on the work unit rejects a duplicate
assign before one is attempted. A returned assignment carries a handoff block
naming the required profile, namespace, and the actions that stay forbidden.

Authorization reuses the #633 model rather than adding a second one. The new
initiate_workflow action is operator-class because its outcome is a claim, not
a Gitea verdict: requesting reviewer or merger work reserves that work but
grants no right to approve or merge. Execution is gated by a new per-action
execution_env_flag (WEBUI_REQUESTS_EXECUTION), deliberately in place of raising
ACTIVE_PHASE - a phase bump would enable execution for every phase-2 action at
once, including ones whose execution path is not implemented. Actions that
declare no flag are unchanged and still report execution_enabled false.

Every preview and apply emits a console audit record correlated to the
resulting assignment by correlation.request_id.

Fail-closed throughout: an unreadable control-plane DB, an incomplete queue
inventory (#758), an allocator that raises, an unpinned PR head, a moved PR
head, and an unconfirmed apply all deny without mutating.

Files:
- webui/request_service.py (new) - request model, preview, initiation
- webui/request_views.py (new) - form and preview rendering, escaped
- tests/test_webui_request_initiation.py (new) - 52 tests
- webui/console_authz.py - initiate_workflow action, execution_wired()
- webui/app.py - /requests, /api/v1/requests/preview, /api/v1/requests/apply
- webui/nav.py - Requests nav entry
- webui/traffic_loader.py - public candidates_from_queue_snapshot alias
- docs/webui-requests.md (new), docs/webui-authz-audit.md

Validation: full suite on this branch 5242 passed, 6 skipped, 899 subtests, 23
failed. Clean master baseline at 2f4dec83 in an equivalent branches/ worktree:
5190 passed, 6 skipped, 867 subtests, the same 23 tests failed. The branch adds
52 passing tests and introduces no new full-suite failure signature.

Closes #643

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-25 01:47:38 -04:00

918 lines
32 KiB
Python

"""Request preview, authorization, and workflow initiation tests (#643).
Covers each acceptance criterion:
* AC1 — preview shows authorize/deny with reasons.
* AC2 — apply creates an exclusive assignment or returns wait/blocked.
* AC3 — duplicate assign rejected.
* AC4 — preview / apply / deny / collision are all exercised.
* AC5 — the UI never renders a secret, and messaging stays brief.
Required tests named in the issue: allocator integration with fakes, and
gated-action tests. The allocator is injected as a fake throughout so no test
touches Gitea or reserves real work; one class asserts the *real* default
allocator refuses an incomplete inventory rather than ranking a partial set.
"""
from __future__ import annotations
import json
import os
import pathlib
import sys
import tempfile
import unittest
from typing import Any
from unittest import mock
from tests.webui_testclient import TestClient
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
import allocator_service # noqa: E402
from webui import console_audit, console_authz, request_service # noqa: E402
from webui.app import create_app # noqa: E402
from webui.console_redaction import scan_for_secrets # noqa: E402
from webui.request_views import render_requests_page # noqa: E402
EXEC_FLAG = "WEBUI_REQUESTS_EXECUTION"
SCOPE = {
"remote": "prgs",
"org": "Scaled-Tech-Consulting",
"repo": "Gitea-Tools",
}
def _principal(role: str) -> console_authz.Principal:
return console_authz.Principal(
subject=f"{role}@example.com",
role=role,
identity_source=console_authz.IDENTITY_ACCESS_PROXY,
authenticated=True,
)
def _request(
*,
role: str = "author",
kind: str = "issue",
number: int = 643,
intent: str = "implement request preview and initiation",
head: str | None = None,
) -> request_service.WorkRequest:
parsed, error = request_service.parse_request(
{
"desired_role": role,
"work_kind": kind,
"work_number": number,
"intent_summary": intent,
"expected_head_sha": head,
**SCOPE,
}
)
assert error is None, error
assert parsed is not None
return parsed
def _selection(
*, kind: str = "issue", number: int = 643, head_sha: str | None = None
) -> dict[str, Any]:
return {
"kind": kind,
"number": number,
"title": "Web Console: Requests, intent preview, authorization",
"head_sha": head_sha,
"selected_action": "implement",
"expected_role_next": "author",
}
def _fake_allocator(
*,
selection: dict[str, Any] | None = None,
preview_outcome: str = allocator_service.OUTCOME_PREVIEW,
apply_outcome: str = allocator_service.OUTCOME_ASSIGNED,
assignment: dict[str, Any] | None = None,
calls: list[dict[str, Any]] | None = None,
):
"""Build an allocator double that records how it was called."""
chosen = selection if selection is not None else _selection()
made = (
assignment
if assignment is not None
else {
"assignment_id": "asn-test-0001",
"lease_id": "lease-test-0001",
"session_id": "webui-request-test",
"expected_head_sha": chosen.get("head_sha"),
}
)
def _allocator(*, request, apply, expected_candidate_set_fingerprint=None):
if calls is not None:
calls.append(
{
"apply": apply,
"role": request.desired_role,
"fingerprint": expected_candidate_set_fingerprint,
}
)
return {
"outcome": apply_outcome if apply else preview_outcome,
"selected": dict(chosen),
"reasons": ["fake allocator"],
"candidate_set_fingerprint": "fp-test",
"candidate_count": 3,
"inventory_complete": True,
"selection_policy": allocator_service.SELECTION_POLICY,
"substrate": "control_plane_db",
"assignment": dict(made) if apply else None,
}
return _allocator
def _no_claims(_request):
return {}
def _claimed(role: str = "author"):
def _source(request):
return {
request.work_key: {
"lease_id": "lease-foreign-9999",
"session_id": "prgs-author-999-foreign",
"role": role,
"expires_at": "2026-07-25T09:10:37Z",
}
}
return _source
class TestRequestParsing(unittest.TestCase):
"""The request model rejects rather than guesses."""
def test_valid_request_round_trips(self):
req = _request()
self.assertEqual(req.work_key, ("issue", 643))
self.assertEqual(req.display_ref, "#643")
self.assertEqual(req.to_dict()["desired_role"], "author")
def test_unknown_role_rejected(self):
parsed, error = request_service.parse_request(
{
"desired_role": "admin",
"work_kind": "issue",
"work_number": 1,
"intent_summary": "x",
**SCOPE,
}
)
self.assertIsNone(parsed)
self.assertEqual(error.reason_code, "unknown_role")
self.assertEqual(error.field_name, "desired_role")
def test_unknown_work_kind_rejected(self):
parsed, error = request_service.parse_request(
{
"desired_role": "author",
"work_kind": "branch",
"work_number": 1,
"intent_summary": "x",
**SCOPE,
}
)
self.assertIsNone(parsed)
self.assertEqual(error.reason_code, "unknown_work_kind")
def test_non_positive_number_rejected(self):
for value in (0, -3):
with self.subTest(value=value):
parsed, error = request_service.parse_request(
{
"desired_role": "author",
"work_kind": "issue",
"work_number": value,
"intent_summary": "x",
**SCOPE,
}
)
self.assertIsNone(parsed)
self.assertEqual(error.reason_code, "invalid_work_number")
def test_missing_intent_rejected(self):
parsed, error = request_service.parse_request(
{
"desired_role": "author",
"work_kind": "issue",
"work_number": 1,
**SCOPE,
}
)
self.assertIsNone(parsed)
self.assertEqual(error.reason_code, "missing_intent")
def test_intent_is_bounded(self):
req = _request(intent="x" * 5000)
self.assertEqual(len(req.intent_summary), request_service.MAX_INTENT_CHARS)
def test_unresolved_scope_rejected(self):
parsed, error = request_service.parse_request(
{
"desired_role": "author",
"work_kind": "issue",
"work_number": 1,
"intent_summary": "x",
}
)
self.assertIsNone(parsed)
self.assertEqual(error.reason_code, "scope_unresolved")
def test_default_scope_fills_missing_fields(self):
parsed, error = request_service.parse_request(
{
"desired_role": "author",
"work_kind": "issue",
"work_number": 7,
"intent_summary": "x",
},
default_scope=SCOPE,
)
self.assertIsNone(error)
self.assertEqual(parsed.repo, "Gitea-Tools")
class TestPreviewAuthorizeDeny(unittest.TestCase):
"""AC1 — preview shows authorize/deny with reasons."""
def test_authorized_preview_names_every_check(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
self.assertTrue(preview.authorized)
self.assertEqual(
{c.name for c in preview.checks},
{
request_service.CHECK_AUTHORIZATION,
request_service.CHECK_CAPABILITY,
request_service.CHECK_LEASE_AVAILABILITY,
request_service.CHECK_NEXT_SAFE_ACTION,
request_service.CHECK_HEAD_PIN,
},
)
self.assertEqual(preview.required_profile, "prgs-author")
self.assertEqual(preview.required_namespace, "gitea-author")
def test_every_check_carries_a_reason(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
for check in preview.checks:
with self.subTest(check=check.name):
self.assertTrue(check.reason_code.strip())
self.assertTrue(check.detail.strip())
def test_anonymous_preview_denied_with_reason(self):
preview = request_service.preview_request(
_request(),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(preview.reason_code, console_authz.DENY_UNAUTHENTICATED)
def test_viewer_preview_denied_for_insufficient_role(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.VIEWER),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(preview.reason_code, console_authz.DENY_INSUFFICIENT_ROLE)
def test_denied_preview_never_reaches_the_allocator(self):
"""A denial must not double as a queue oracle."""
calls: list[dict[str, Any]] = []
request_service.preview_request(
_request(),
principal=_principal(console_authz.VIEWER),
allocator=_fake_allocator(calls=calls),
claims_source=_no_claims,
audit=False,
)
self.assertEqual(calls, [])
def test_preview_lists_prohibited_actions(self):
preview = request_service.preview_request(
_request(role="author"),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
self.assertIn("merge", preview.prohibited_actions)
self.assertIn("approve", preview.prohibited_actions)
def test_preview_reports_next_safe_action(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
self.assertIn("issue #643", preview.next_safe_action)
def test_preview_never_mutates(self):
calls: list[dict[str, Any]] = []
request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(calls=calls),
claims_source=_no_claims,
audit=False,
)
self.assertEqual([c["apply"] for c in calls], [False])
class TestPreviewFailClosed(unittest.TestCase):
"""Missing evidence denies; it never reads as an absence of obstacles."""
def test_unreadable_claim_inventory_denies(self):
def _boom(_request):
raise RuntimeError("db unavailable")
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_boom,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(
preview.reason_code, request_service.REASON_EVIDENCE_UNAVAILABLE
)
def test_allocator_failure_denies(self):
def _boom(**_kwargs):
raise RuntimeError("allocator exploded")
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_boom,
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(
preview.reason_code, request_service.REASON_EVIDENCE_UNAVAILABLE
)
def test_allocator_selecting_other_work_denies(self):
preview = request_service.preview_request(
_request(number=643),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(selection=_selection(number=999)),
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(preview.reason_code, request_service.REASON_NOT_NEXT_SAFE)
self.assertIn("#999", preview.detail)
def test_pr_without_head_sha_denies(self):
preview = request_service.preview_request(
_request(role="reviewer", kind="pr", number=898),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(
selection=_selection(kind="pr", number=898, head_sha=None)
),
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(
preview.reason_code, request_service.REASON_EVIDENCE_UNAVAILABLE
)
def test_pr_head_moved_denies(self):
preview = request_service.preview_request(
_request(role="reviewer", kind="pr", number=898, head="a" * 40),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(
selection=_selection(kind="pr", number=898, head_sha="b" * 40)
),
claims_source=_no_claims,
audit=False,
)
self.assertFalse(preview.authorized)
self.assertEqual(preview.reason_code, "head_moved")
def test_pr_head_matching_passes(self):
preview = request_service.preview_request(
_request(role="reviewer", kind="pr", number=898, head="b" * 40),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(
selection=_selection(kind="pr", number=898, head_sha="b" * 40)
),
claims_source=_no_claims,
audit=False,
)
self.assertTrue(preview.authorized)
class TestApplyExecutionGate(unittest.TestCase):
"""Execution stays wired off unless an operator opts in explicitly."""
def test_action_is_registered_and_unwired_by_default(self):
action = console_authz.get_action(request_service.ACTION_ID)
self.assertIsNotNone(action)
self.assertEqual(action.phase, 2)
self.assertEqual(action.minimum_role, console_authz.OPERATOR)
self.assertTrue(action.requires_confirmation)
self.assertFalse(console_authz.execution_wired(action, env={}))
def test_flag_named_but_unset_does_not_wire(self):
action = console_authz.get_action(request_service.ACTION_ID)
self.assertFalse(console_authz.execution_wired(action, env={EXEC_FLAG: "no"}))
self.assertTrue(console_authz.execution_wired(action, env={EXEC_FLAG: "1"}))
def test_opting_in_wires_only_this_action(self):
env = {EXEC_FLAG: "1"}
for action_id, action in console_authz.ACTIONS.items():
with self.subTest(action=action_id):
self.assertEqual(
console_authz.execution_wired(action, env=env),
action_id == request_service.ACTION_ID,
)
def test_apply_denied_while_unwired(self):
with mock.patch.dict(os.environ, {EXEC_FLAG: ""}):
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertEqual(result["outcome"], request_service.OUTCOME_DENIED)
self.assertEqual(result["reason_code"], request_service.REASON_UNAUTHORIZED)
self.assertFalse(result["mutation_performed"])
class TestApplyOutcomes(unittest.TestCase):
"""AC2/AC3/AC4 — assignment, wait, blocked, and duplicate rejection."""
def setUp(self):
patcher = mock.patch.dict(os.environ, {EXEC_FLAG: "1"})
patcher.start()
self.addCleanup(patcher.stop)
def test_apply_creates_exclusive_assignment(self):
calls: list[dict[str, Any]] = []
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(calls=calls),
claims_source=_no_claims,
)
self.assertTrue(result["ok"])
self.assertEqual(result["outcome"], allocator_service.OUTCOME_ASSIGNED)
self.assertEqual(result["assignment"]["assignment_id"], "asn-test-0001")
self.assertTrue(result["mutation_performed"])
self.assertEqual(result["status_code"], 201)
# Dry-run first, then apply — never apply alone.
self.assertEqual([c["apply"] for c in calls], [False, True])
# The apply call carries the fingerprint the dry-run produced.
self.assertEqual(calls[1]["fingerprint"], "fp-test")
def test_assignment_returns_a_role_handoff(self):
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
handoff = result["handoff"]
self.assertEqual(handoff["required_profile"], "prgs-author")
self.assertEqual(handoff["required_namespace"], "gitea-author")
self.assertEqual(handoff["assignment_id"], "asn-test-0001")
self.assertIn("merge", handoff["forbidden_actions"])
def test_unconfirmed_apply_refuses_before_the_allocator(self):
calls: list[dict[str, Any]] = []
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=False,
allocator=_fake_allocator(calls=calls),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertEqual(
result["reason_code"], request_service.REASON_CONFIRMATION_REQUIRED
)
self.assertEqual(calls, [])
def test_duplicate_assignment_rejected(self):
"""AC3 — an active lease on the work unit blocks a second assign."""
calls: list[dict[str, Any]] = []
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(calls=calls),
claims_source=_claimed(),
)
self.assertFalse(result["ok"])
self.assertEqual(result["outcome"], request_service.OUTCOME_BLOCKED)
self.assertEqual(
result["reason_code"], request_service.REASON_DUPLICATE_ASSIGNMENT
)
self.assertFalse(result["mutation_performed"])
# The dry-run ran; the apply never did.
self.assertEqual([c["apply"] for c in calls], [False])
def test_not_next_safe_work_returns_wait_without_applying(self):
calls: list[dict[str, Any]] = []
result = request_service.apply_request(
_request(number=643),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(
selection=_selection(number=999), calls=calls
),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertEqual(result["outcome"], request_service.OUTCOME_WAIT)
self.assertEqual(result["reason_code"], request_service.REASON_NOT_NEXT_SAFE)
self.assertEqual([c["apply"] for c in calls], [False])
def test_allocator_declining_on_apply_returns_blocked(self):
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(
apply_outcome=allocator_service.OUTCOME_BLOCKED_LEASE,
assignment={},
),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertEqual(result["outcome"], request_service.OUTCOME_BLOCKED)
self.assertEqual(
result["reason_code"], request_service.REASON_ALLOCATOR_OUTCOME
)
self.assertFalse(result["mutation_performed"])
def test_allocator_drift_on_apply_is_not_read_as_an_assignment(self):
"""The apply call must return *this* work unit, not a substitute."""
def _drifting(*, request, apply, expected_candidate_set_fingerprint=None):
return {
"outcome": (
allocator_service.OUTCOME_ASSIGNED
if apply
else allocator_service.OUTCOME_PREVIEW
),
"selected": _selection(number=999 if apply else 643),
"assignment": {"assignment_id": "asn-wrong"} if apply else None,
"candidate_set_fingerprint": "fp-test",
}
result = request_service.apply_request(
_request(number=643),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_drifting,
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertIsNone(result["assignment"])
self.assertFalse(result["mutation_performed"])
def test_viewer_cannot_apply(self):
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.VIEWER),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertEqual(result["reason_code"], request_service.REASON_UNAUTHORIZED)
def test_anonymous_cannot_apply(self):
result = request_service.apply_request(
_request(),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
self.assertFalse(result["ok"])
self.assertFalse(result["mutation_performed"])
class TestAllocatorIntegrationFakes(unittest.TestCase):
"""The real default allocator refuses a partial inventory (#758)."""
def test_incomplete_inventory_returns_none(self):
from webui.queue_loader import PaginationMeta, QueueSnapshot
snapshot = QueueSnapshot(
project_id="p",
repo_label="r",
prs=(),
issues=(),
pr_pagination=PaginationMeta(
page=1,
per_page=50,
returned_count=50,
has_more=True,
is_final_page=False,
inventory_complete=False,
pages_fetched=1,
),
issue_pagination=None,
)
with mock.patch(
"webui.queue_loader.load_queue_snapshot", return_value=snapshot
):
result = request_service.default_allocator(
request=_request(), apply=False
)
self.assertIsNone(result)
def test_fetch_error_returns_none(self):
from webui.queue_loader import QueueSnapshot
snapshot = QueueSnapshot(
project_id="p",
repo_label="r",
prs=(),
issues=(),
pr_pagination=None,
issue_pagination=None,
fetch_error="no credentials",
)
with mock.patch(
"webui.queue_loader.load_queue_snapshot", return_value=snapshot
):
result = request_service.default_allocator(
request=_request(), apply=True
)
self.assertIsNone(result)
class TestAuditRecords(unittest.TestCase):
"""Every preview and apply is auditable, correlated, and redacted."""
def setUp(self):
handle = tempfile.NamedTemporaryFile(
mode="w", suffix=".jsonl", delete=False
)
handle.close()
self.sink = handle.name
self.addCleanup(
lambda: os.path.exists(self.sink) and os.remove(self.sink)
)
patcher = mock.patch.dict(
os.environ, {console_audit.AUDIT_LOG_ENV: self.sink}
)
patcher.start()
self.addCleanup(patcher.stop)
def _records(self) -> list[dict[str, Any]]:
with open(self.sink, encoding="utf-8") as handle:
return [json.loads(line) for line in handle if line.strip()]
def test_preview_is_audited_with_a_correlation_id(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
)
records = self._records()
self.assertEqual(len(records), 1)
record = records[0]
self.assertEqual(record["action"], request_service.ACTION_ID)
self.assertEqual(record["result"], console_audit.RESULT_PREVIEWED)
self.assertEqual(
record["correlation"]["request_id"], preview.correlation_id
)
self.assertEqual(record["target"]["ref"], "#643")
def test_denied_apply_is_audited(self):
with mock.patch.dict(os.environ, {EXEC_FLAG: ""}):
request_service.apply_request(
_request(),
principal=_principal(console_authz.VIEWER),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
self.assertEqual(self._records()[-1]["result"], console_audit.RESULT_DENIED)
def test_assignment_is_audited_and_correlated(self):
with mock.patch.dict(os.environ, {EXEC_FLAG: "1"}):
result = request_service.apply_request(
_request(),
principal=_principal(console_authz.OPERATOR),
confirm=True,
allocator=_fake_allocator(),
claims_source=_no_claims,
)
record = self._records()[-1]
self.assertEqual(record["result"], console_audit.RESULT_SUCCEEDED)
self.assertEqual(
record["correlation"]["request_id"], result["correlation_id"]
)
self.assertEqual(
record["metadata"]["assignment_id"],
result["assignment"]["assignment_id"],
)
def test_intent_bearing_a_secret_is_not_persisted_raw(self):
request_service.preview_request(
_request(intent="use token=ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
)
for record in self._records():
with self.subTest(event=record.get("event_id")):
self.assertFalse(scan_for_secrets(record))
class TestRequestRoutes(unittest.TestCase):
"""The HTTP surface: form page, preview API, apply API."""
def setUp(self):
self.client = TestClient(create_app())
def test_requests_page_renders_form(self):
response = self.client.get("/requests")
self.assertEqual(response.status_code, 200)
body = response.text
self.assertIn("Requests", body)
self.assertIn("desired_role", body)
self.assertIn("intent_summary", body)
def test_requests_page_is_linked_from_nav(self):
from webui.nav import nav_hrefs
self.assertIn("/requests", nav_hrefs())
def test_preview_api_rejects_an_invalid_request(self):
response = self.client.post(
"/api/v1/requests/preview",
json={
"desired_role": "wizard",
"work_kind": "issue",
"work_number": 1,
"intent_summary": "x",
**SCOPE,
},
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()["reason_code"], "unknown_role")
def test_preview_api_denies_anonymous(self):
response = self.client.post(
"/api/v1/requests/preview",
json={
"desired_role": "author",
"work_kind": "issue",
"work_number": 643,
"intent_summary": "x",
**SCOPE,
},
)
self.assertEqual(response.status_code, 403)
payload = response.json()
self.assertFalse(payload["authorized"])
self.assertFalse(payload["mutation_performed"])
def test_apply_api_denies_anonymous(self):
response = self.client.post(
"/api/v1/requests/apply",
json={
"desired_role": "author",
"work_kind": "issue",
"work_number": 643,
"intent_summary": "x",
"confirm": True,
**SCOPE,
},
)
self.assertEqual(response.status_code, 403)
payload = response.json()
self.assertFalse(payload["ok"])
self.assertFalse(payload["mutation_performed"])
self.assertIsNone(payload["assignment"])
def test_apply_api_rejects_an_invalid_request(self):
response = self.client.post(
"/api/v1/requests/apply",
json={
"desired_role": "author",
"work_kind": "issue",
"work_number": -1,
"intent_summary": "x",
**SCOPE,
},
)
self.assertEqual(response.status_code, 400)
def test_request_apis_are_post_only(self):
"""GET is not a way in. The app's 405 handler renders a read-only
method against a write route as 404, so that is what is asserted."""
for path in ("/api/v1/requests/preview", "/api/v1/requests/apply"):
with self.subTest(path=path):
self.assertEqual(self.client.get(path).status_code, 404)
def test_form_post_previews_and_never_assigns(self):
response = self.client.post(
"/requests",
data={
"desired_role": "author",
"work_kind": "issue",
"work_number": "643",
"intent_summary": "implement the request surface",
"remote": "prgs",
"org": "Scaled-Tech-Consulting",
"repo": "Gitea-Tools",
},
)
self.assertEqual(response.status_code, 200)
self.assertIn("Intent preview", response.text)
class TestRenderingSafety(unittest.TestCase):
"""AC5 — the page escapes hostile input and shows no secret."""
def test_intent_is_escaped(self):
preview = request_service.preview_request(
_request(intent="<script>alert(1)</script>"),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
html = render_requests_page(preview=preview)
self.assertNotIn("<script>alert(1)</script>", html)
self.assertIn("&lt;script&gt;", html)
def test_page_renders_a_denial_without_a_preview(self):
_, error = request_service.parse_request(
{
"desired_role": "wizard",
"work_kind": "issue",
"work_number": 1,
"intent_summary": "x",
**SCOPE,
}
)
html = render_requests_page(error=error)
self.assertIn("Request rejected", html)
self.assertIn("unknown_role", html)
def test_page_shows_no_credential_material(self):
preview = request_service.preview_request(
_request(),
principal=_principal(console_authz.OPERATOR),
allocator=_fake_allocator(),
claims_source=_no_claims,
audit=False,
)
html = render_requests_page(preview=preview)
for needle in ("token=", "Bearer ", "password"):
with self.subTest(needle=needle):
self.assertNotIn(needle, html)
if __name__ == "__main__": # pragma: no cover
unittest.main()