|
| 1 | +from typing import Any |
| 2 | + |
| 3 | +from flask import url_for |
| 4 | +from flask.testing import FlaskClient |
| 5 | +from flask_sqlalchemy import SQLAlchemy |
| 6 | +from pytest_mock import MockerFixture |
| 7 | + |
| 8 | +from api.models import ( |
| 9 | + OktaGroup, |
| 10 | + OktaUser, |
| 11 | +) |
| 12 | +from api.operations.constraints.check_for_reason import CheckForReason |
| 13 | + |
| 14 | + |
| 15 | +def test_invalid_reason_with_template(mocker: MockerFixture) -> None: |
| 16 | + """Test that the template itself and templates with placeholders are considered invalid reasons.""" |
| 17 | + |
| 18 | + # Mock the access_config to return a specific template |
| 19 | + mock_access_config = mocker.patch("api.operations.constraints.check_for_reason.get_access_config") |
| 20 | + mock_config = mocker.MagicMock() |
| 21 | + mock_config.reason_template = ( |
| 22 | + "Project: [Project Name]\nTicket: [Ticket ID]\nJustification: [Why is this access needed?]" |
| 23 | + ) |
| 24 | + mock_config.reason_template_required = ["Project", "Ticket", "Justification"] |
| 25 | + mock_access_config.return_value = mock_config |
| 26 | + |
| 27 | + # Test cases |
| 28 | + # 1. Empty reason |
| 29 | + assert CheckForReason.invalid_reason(None) is True |
| 30 | + assert CheckForReason.invalid_reason("") is True |
| 31 | + assert CheckForReason.invalid_reason(" ") is True |
| 32 | + |
| 33 | + # 2. Template as-is (unchanged) |
| 34 | + template = "Project: [Project Name]\nTicket: [Ticket ID]\nJustification: [Why is this access needed?]" |
| 35 | + assert CheckForReason.invalid_reason(template) is True |
| 36 | + |
| 37 | + # 3. Template with missing fields |
| 38 | + template = "Project: [Project Name]\nTicket: [Ticket ID]" |
| 39 | + assert CheckForReason.invalid_reason(template) is True |
| 40 | + |
| 41 | + # 4. Template with all placeholders filled should be valid |
| 42 | + filled_template = "Project: My Project\nTicket: TICKET-123\nJustification: I need access to deploy code" |
| 43 | + assert CheckForReason.invalid_reason(filled_template) is False |
| 44 | + |
| 45 | + # 5. Completely different invalid reason |
| 46 | + valid_reason = "I need access for the new project launch" |
| 47 | + assert CheckForReason.invalid_reason(valid_reason) is True |
| 48 | + |
| 49 | + |
| 50 | +def test_reason_validation_in_request_endpoint( |
| 51 | + client: FlaskClient, |
| 52 | + db: SQLAlchemy, |
| 53 | + mocker: MockerFixture, |
| 54 | + okta_group: OktaGroup, |
| 55 | + user: OktaUser, |
| 56 | +) -> None: |
| 57 | + """Test that the API endpoints reject reasons that just contain the template.""" |
| 58 | + |
| 59 | + # Mock the access_config to return a specific template |
| 60 | + mock_access_config = mocker.patch("api.views.schemas.access_requests.get_access_config") |
| 61 | + mock_config = mocker.MagicMock() |
| 62 | + mock_config.reason_template = ( |
| 63 | + "Project: [Project Name]\nTicket: [Ticket ID]\nJustification: [Why is this access needed?]" |
| 64 | + ) |
| 65 | + mock_config.reason_template_required = ["Project", "Ticket", "Justification"] |
| 66 | + mock_access_config.return_value = mock_config |
| 67 | + |
| 68 | + # Set up the group and user |
| 69 | + db.session.add(okta_group) |
| 70 | + db.session.add(user) |
| 71 | + db.session.commit() |
| 72 | + |
| 73 | + # Try creating an access request with the template as reason |
| 74 | + template = "Project: [Project Name]\nTicket: [Ticket ID]" |
| 75 | + |
| 76 | + data: dict[str, Any] = { |
| 77 | + "group_id": okta_group.id, |
| 78 | + "group_owner": False, |
| 79 | + "reason": template, |
| 80 | + } |
| 81 | + |
| 82 | + # Create the access request |
| 83 | + access_request_url = url_for("api-access-requests.access_requests") |
| 84 | + rep = client.post(access_request_url, json=data) |
| 85 | + |
| 86 | + # Should get rejected because the reason it is missing required information |
| 87 | + assert rep.status_code == 400 |
| 88 | + |
| 89 | + # Try again with a filled template |
| 90 | + filled_template = "Project: My Project\nTicket: TICKET-123\nJustification: I need access to deploy code" |
| 91 | + data["reason"] = filled_template |
| 92 | + |
| 93 | + rep = client.post(access_request_url, json=data) |
| 94 | + # Should succeed with a properly filled template |
| 95 | + assert rep.status_code == 201 |
0 commit comments