1295dea0ac
Changed DiffDisplayMode enum values from side_by_side/context to split/auto as required by specification §29570, §30139, §30391. - Renamed SIDE_BY_SIDE="side_by_side" → SPLIT="split" in models.py - Renamed CONTEXT="context" → AUTO="auto" in models.py - Renamed side_by_side_diff() → split_diff() in models.py - Renamed context_diff() → auto_diff() in models.py - Updated _DIFF_MODE_CYCLE in screen.py to use SPLIT and AUTO - Updated all BDD feature scenarios and step definitions - Fixed broken Behave step parameter renames (restored standard 'context' param) Closes #1449 --- Automated by CleverAgents Bot Supervisor: Implementation | Agent: task-implementor
376 lines
17 KiB
Gherkin
376 lines
17 KiB
Gherkin
Feature: TUI PermissionsScreen
|
||
Scenarios exercising the PermissionsScreen widget and supporting
|
||
domain models / service for tool permission requests.
|
||
|
||
# ── DiffDisplayMode enum ──────────────────────────────────────
|
||
|
||
Scenario: DiffDisplayMode has three values
|
||
When I inspect the DiffDisplayMode enum
|
||
Then DiffDisplayMode should have values "unified", "split", "auto"
|
||
|
||
# ── FileChangeType enum ───────────────────────────────────────
|
||
|
||
Scenario: FileChangeType has three values
|
||
When I inspect the FileChangeType enum
|
||
Then FileChangeType should have values "M", "A", "D"
|
||
|
||
# ── PermissionDecision enum ───────────────────────────────────
|
||
|
||
Scenario: PermissionDecision has four values
|
||
When I inspect the PermissionDecision enum
|
||
Then PermissionDecision should have values "allow_once", "allow_always", "reject_once", "reject_always"
|
||
|
||
# ── PermissionRequest model ───────────────────────────────────
|
||
|
||
Scenario: PermissionRequest generates a unified diff
|
||
Given a PermissionRequest for "src/auth/handler.py" with before and after content
|
||
When I render the diff in "unified" mode
|
||
Then the rendered diff should contain "@@"
|
||
And the rendered diff should contain "+"
|
||
And the rendered diff should contain "-"
|
||
|
||
Scenario: PermissionRequest generates a split diff
|
||
Given a PermissionRequest for "src/auth/handler.py" with before and after content
|
||
When I render the diff in "split" mode
|
||
Then the rendered diff should contain "|"
|
||
|
||
Scenario: PermissionRequest generates an auto diff
|
||
Given a PermissionRequest for "src/auth/handler.py" with before and after content
|
||
When I render the diff in "auto" mode
|
||
Then the rendered diff should contain "***"
|
||
|
||
Scenario: PermissionRequest unified diff with no changes returns empty string
|
||
Given a PermissionRequest for "src/empty.py" with identical before and after content
|
||
When I render the diff in "unified" mode
|
||
Then the rendered diff should be empty
|
||
|
||
Scenario: PermissionRequest split diff returns equal lines for identical content
|
||
Given a PermissionRequest for "src/empty.py" with identical before and after content
|
||
When I call split_diff on the request
|
||
Then both sides should have the same number of lines
|
||
|
||
Scenario: PermissionRequest auto_diff with no changes returns empty string
|
||
Given a PermissionRequest for "src/empty.py" with identical before and after content
|
||
When I render the diff in "auto" mode
|
||
Then the rendered diff should be empty
|
||
|
||
Scenario: PermissionRequest render_diff dispatches to unified
|
||
Given a PermissionRequest for "src/auth/handler.py" with before and after content
|
||
When I render the diff in "unified" mode
|
||
Then the rendered diff should contain "a/src/auth/handler.py"
|
||
|
||
Scenario: PermissionRequest render_diff dispatches to auto
|
||
Given a PermissionRequest for "src/auth/handler.py" with before and after content
|
||
When I render the diff in "auto" mode
|
||
Then the rendered diff should contain "a/src/auth/handler.py"
|
||
|
||
# ── ToolPermissionRequest model ───────────────────────────────
|
||
|
||
Scenario: ToolPermissionRequest is pending when no decision is set
|
||
Given a ToolPermissionRequest with no decision
|
||
Then the request should be pending
|
||
And the request should not be allowed
|
||
And the request should not be rejected
|
||
|
||
Scenario: ToolPermissionRequest is allowed after allow_once decision
|
||
Given a ToolPermissionRequest with no decision
|
||
When I apply decision "allow_once" to the request
|
||
Then the request should be allowed
|
||
And the request should not be pending
|
||
|
||
Scenario: ToolPermissionRequest is allowed after allow_always decision
|
||
Given a ToolPermissionRequest with no decision
|
||
When I apply decision "allow_always" to the request
|
||
Then the request should be allowed
|
||
|
||
Scenario: ToolPermissionRequest is rejected after reject_once decision
|
||
Given a ToolPermissionRequest with no decision
|
||
When I apply decision "reject_once" to the request
|
||
Then the request should be rejected
|
||
And the request should not be pending
|
||
|
||
Scenario: ToolPermissionRequest is rejected after reject_always decision
|
||
Given a ToolPermissionRequest with no decision
|
||
When I apply decision "reject_always" to the request
|
||
Then the request should be rejected
|
||
|
||
Scenario: ToolPermissionRequest apply_decision returns a new instance
|
||
Given a ToolPermissionRequest with no decision
|
||
When I apply decision "allow_once" to the request
|
||
Then the original request should still be pending
|
||
|
||
# ── PermissionRequestService ──────────────────────────────────
|
||
|
||
Scenario: PermissionRequestService enqueues a request
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
Then the service should have 1 pending request
|
||
|
||
Scenario: PermissionRequestService records allow_once decision
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_once" for request "req-1"
|
||
Then the service should have 0 pending requests
|
||
And the request "req-1" should have decision "allow_once"
|
||
|
||
Scenario: PermissionRequestService records reject_once decision
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "reject_once" for request "req-1"
|
||
Then the service should have 0 pending requests
|
||
And the request "req-1" should have decision "reject_once"
|
||
|
||
Scenario: PermissionRequestService auto-resolves with allow_always session decision
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_always" for request "req-1"
|
||
And I enqueue a request with id "req-2" for tool "local/file-write"
|
||
Then the service should have 0 pending requests
|
||
And the request "req-2" should have decision "allow_always"
|
||
|
||
Scenario: PermissionRequestService auto-resolves with reject_always session decision
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "reject_always" for request "req-1"
|
||
And I enqueue a request with id "req-2" for tool "local/file-write"
|
||
Then the service should have 0 pending requests
|
||
And the request "req-2" should have decision "reject_always"
|
||
|
||
Scenario: PermissionRequestService get_session_decision returns None when not set
|
||
Given a fresh PermissionRequestService
|
||
Then the session decision for "local/file-write" should be None
|
||
|
||
Scenario: PermissionRequestService get_session_decision returns decision after allow_always
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_always" for request "req-1"
|
||
Then the session decision for "local/file-write" should be "allow_always"
|
||
|
||
Scenario: PermissionRequestService clear_session_decision removes the decision
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_always" for request "req-1"
|
||
And I clear the session decision for "local/file-write"
|
||
Then the session decision for "local/file-write" should be None
|
||
|
||
Scenario: PermissionRequestService clear_session_decision returns False when not set
|
||
Given a fresh PermissionRequestService
|
||
When I clear the session decision for "local/file-write"
|
||
Then the clear result should be False
|
||
|
||
Scenario: PermissionRequestService record_decision returns None for unknown id
|
||
Given a fresh PermissionRequestService
|
||
When I record decision "allow_once" for request "nonexistent"
|
||
Then the decision result should be None
|
||
|
||
Scenario: PermissionRequestService get returns None for unknown id
|
||
Given a fresh PermissionRequestService
|
||
Then getting request "nonexistent" should return None
|
||
|
||
Scenario: PermissionRequestService all_requests returns all requests
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I enqueue a request with id "req-2" for tool "local/file-write"
|
||
Then the service should have 2 total requests
|
||
|
||
Scenario: PermissionRequestService clear_all removes everything
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_always" for request "req-1"
|
||
And I clear all requests
|
||
Then the service should have 0 total requests
|
||
And the session decision for "local/file-write" should be None
|
||
|
||
# ── PermissionsScreen widget ──────────────────────────────────
|
||
|
||
Scenario: PermissionsScreen loads a request and renders content
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the screen text should contain "Permission Request"
|
||
And the screen text should contain "Files (3 changes)"
|
||
And the screen text should contain "local/file-write"
|
||
|
||
Scenario: PermissionsScreen shows the first file selected by default
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the selected index should be 0
|
||
And the screen text should contain "❯"
|
||
|
||
Scenario: PermissionsScreen navigate_next moves to next file
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I navigate next on the screen
|
||
Then the selected index should be 1
|
||
|
||
Scenario: PermissionsScreen navigate_prev wraps around
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I navigate prev on the screen
|
||
Then the selected index should be 2
|
||
|
||
Scenario: PermissionsScreen navigate_next wraps around at end
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I navigate next on the screen
|
||
And I navigate next on the screen
|
||
And I navigate next on the screen
|
||
Then the selected index should be 0
|
||
|
||
Scenario: PermissionsScreen cycle_diff_mode cycles through modes
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the diff mode should be "unified"
|
||
When I cycle the diff mode
|
||
Then the diff mode should be "split"
|
||
When I cycle the diff mode
|
||
Then the diff mode should be "auto"
|
||
When I cycle the diff mode
|
||
Then the diff mode should be "unified"
|
||
|
||
Scenario: PermissionsScreen set_diff_mode sets mode directly
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I set the diff mode to "split"
|
||
Then the diff mode should be "split"
|
||
|
||
Scenario: PermissionsScreen allow_once records decision
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I press allow_once on the screen
|
||
Then the screen decision should be "allow_once"
|
||
|
||
Scenario: PermissionsScreen allow_always records decision
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I press allow_always on the screen
|
||
Then the screen decision should be "allow_always"
|
||
|
||
Scenario: PermissionsScreen reject_once records decision
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I press reject_once on the screen
|
||
Then the screen decision should be "reject_once"
|
||
|
||
Scenario: PermissionsScreen reject_always records decision
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I press reject_always on the screen
|
||
Then the screen decision should be "reject_always"
|
||
|
||
Scenario: PermissionsScreen clear resets state
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
And I press allow_once on the screen
|
||
And I clear the screen
|
||
Then the screen decision should be None
|
||
And the screen text should be empty
|
||
|
||
Scenario: PermissionsScreen with no request shows placeholder
|
||
Given a fresh PermissionsScreen
|
||
Then the screen text should contain "(no permission request)"
|
||
|
||
Scenario: PermissionsScreen navigate_next does nothing with no request
|
||
Given a fresh PermissionsScreen
|
||
When I navigate next on the screen
|
||
Then the selected index should be 0
|
||
|
||
Scenario: PermissionsScreen navigate_prev does nothing with no request
|
||
Given a fresh PermissionsScreen
|
||
When I navigate prev on the screen
|
||
Then the selected index should be 0
|
||
|
||
Scenario: PermissionsScreen shows diff mode in status bar
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the screen text should contain "unified"
|
||
|
||
Scenario: PermissionsScreen shows keyboard bindings in status bar
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the screen text should contain "Allow"
|
||
And the screen text should contain "Reject"
|
||
|
||
Scenario: PermissionsScreen shows diff content for selected file
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the screen text should contain "file0.py"
|
||
|
||
Scenario: PermissionsScreen navigate_next with empty changes list does nothing
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 0 file changes
|
||
And I navigate next on the screen
|
||
Then the selected index should be 0
|
||
|
||
Scenario: PermissionsScreen navigate_prev with empty changes list does nothing
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 0 file changes
|
||
And I navigate prev on the screen
|
||
Then the selected index should be 0
|
||
|
||
# ── _next_diff_mode helper ────────────────────────────────────
|
||
|
||
Scenario: _next_diff_mode cycles unified to split
|
||
When I call _next_diff_mode with "unified"
|
||
Then the next diff mode result should be "split"
|
||
|
||
Scenario: _next_diff_mode cycles split to auto
|
||
When I call _next_diff_mode with "split"
|
||
Then the next diff mode result should be "auto"
|
||
|
||
Scenario: _next_diff_mode cycles auto back to unified
|
||
When I call _next_diff_mode with "auto"
|
||
Then the next diff mode result should be "unified"
|
||
|
||
# ── Rendering helpers ─────────────────────────────────────────
|
||
|
||
Scenario: _render_file_list marks selected file with arrow
|
||
When I render a file list with 2 files and selected index 1
|
||
Then the file list should contain "❯" on the second entry
|
||
|
||
Scenario: _render_diff_panel returns placeholder for None change
|
||
When I render a diff panel with no change
|
||
Then the diff panel should contain "(no file selected)"
|
||
|
||
Scenario: _render_diff_panel returns placeholder for empty diff
|
||
When I render a diff panel with an identical-content change
|
||
Then the diff panel should contain "(no changes)"
|
||
|
||
Scenario: _render_status_bar includes tool name and resource name
|
||
When I render a status bar for tool "local/file-write" and resource "local/api-service"
|
||
Then the status bar should contain "local/file-write"
|
||
And the status bar should contain "local/api-service"
|
||
|
||
# ── PermissionsScreen property accessors ─────────────────────
|
||
|
||
Scenario: PermissionsScreen current_request returns None initially
|
||
Given a fresh PermissionsScreen
|
||
Then the screen current_request should be None
|
||
|
||
Scenario: PermissionsScreen current_request returns loaded request
|
||
Given a fresh PermissionsScreen
|
||
When I load a ToolPermissionRequest with 3 file changes
|
||
Then the screen current_request should not be None
|
||
|
||
# ── PermissionRequest split_diff delete/insert branches ────────
|
||
|
||
Scenario: PermissionRequest split_diff handles delete-only change
|
||
Given a PermissionRequest with only deleted lines
|
||
When I call split_diff on the request
|
||
Then the left side should have non-empty lines
|
||
And the right side should have empty lines for deleted content
|
||
|
||
Scenario: PermissionRequest split_diff handles insert-only change
|
||
Given a PermissionRequest with only inserted lines
|
||
When I call split_diff on the request
|
||
Then the right side should have non-empty lines
|
||
And the left side should have empty lines for inserted content
|
||
|
||
# ── PermissionRequestService clear_session_decision return True ─
|
||
|
||
Scenario: PermissionRequestService clear_session_decision returns True when decision exists
|
||
Given a fresh PermissionRequestService
|
||
When I enqueue a request with id "req-1" for tool "local/file-write"
|
||
And I record decision "allow_always" for request "req-1"
|
||
And I clear the session decision for "local/file-write"
|
||
Then the clear result should be True
|