diff --git a/backend/test_observer/common/constants.py b/backend/test_observer/common/constants.py index 1ab58f6b..15c32ec2 100644 --- a/backend/test_observer/common/constants.py +++ b/backend/test_observer/common/constants.py @@ -1 +1,3 @@ PREVIOUS_TEST_RESULT_COUNT = 10 + +VALID_ISSUE_HOSTS = {"github.com", "warthogs.atlassian.net", "bugs.launchpad.net"} diff --git a/backend/test_observer/controllers/environments/models.py b/backend/test_observer/controllers/environments/models.py index f67a5271..747dd7ac 100644 --- a/backend/test_observer/controllers/environments/models.py +++ b/backend/test_observer/controllers/environments/models.py @@ -1,6 +1,8 @@ from datetime import datetime -from pydantic import BaseModel, HttpUrl +from pydantic import BaseModel, HttpUrl, field_validator + +from test_observer.common.constants import VALID_ISSUE_HOSTS class ReportedIssueRequest(BaseModel): @@ -8,6 +10,15 @@ class ReportedIssueRequest(BaseModel): description: str url: HttpUrl + @field_validator("url") + @classmethod + def name_must_contain_space( + cls: type["ReportedIssueRequest"], url: HttpUrl + ) -> HttpUrl: + if url.host not in VALID_ISSUE_HOSTS: + raise ValueError(f"Issue url must belong to one of {VALID_ISSUE_HOSTS}") + return url + class ReportedIssueResponse(BaseModel): id: int diff --git a/backend/test_observer/controllers/test_cases/models.py b/backend/test_observer/controllers/test_cases/models.py index 78c3fb89..6b70eeae 100644 --- a/backend/test_observer/controllers/test_cases/models.py +++ b/backend/test_observer/controllers/test_cases/models.py @@ -1,6 +1,8 @@ from datetime import datetime -from pydantic import BaseModel, HttpUrl, model_validator +from pydantic import BaseModel, HttpUrl, field_validator, model_validator + +from test_observer.common.constants import VALID_ISSUE_HOSTS class ReportedIssueRequest(BaseModel): @@ -13,8 +15,18 @@ class ReportedIssueRequest(BaseModel): def check_a_or_b(self): if not self.case_name and not self.template_id: raise ValueError("Either case_name or template_id is required") + return self + @field_validator("url") + @classmethod + def name_must_contain_space( + cls: type["ReportedIssueRequest"], url: HttpUrl + ) -> HttpUrl: + if url.host not in VALID_ISSUE_HOSTS: + raise ValueError(f"Issue url must belong to one of {VALID_ISSUE_HOSTS}") + return url + class ReportedIssueResponse(BaseModel): id: int diff --git a/backend/tests/controllers/environments/test_environment_issues.py b/backend/tests/controllers/environments/test_environment_issues.py index b794da4f..321364c3 100644 --- a/backend/tests/controllers/environments/test_environment_issues.py +++ b/backend/tests/controllers/environments/test_environment_issues.py @@ -6,7 +6,7 @@ endpoint = "/v1/environments/reported-issues" valid_post_data = { "environment_name": "template 1", - "url": "http://issue.link/", + "url": "https://github.com/", "description": "some description", } @@ -30,6 +30,17 @@ def test_post_validates_url(test_client: TestClient): assert_fails_validation(response, "url", "url_parsing") +def test_url_cannot_be_canonical_chat(test_client: TestClient): + response = test_client.post( + endpoint, + json={ + **valid_post_data, + "url": "https://chat.canonical.com/canonical/pl/n7oahef13jdpde7p6nf7s5yisw", + }, + ) + assert response.status_code == 422 + + def test_valid_post(test_client: TestClient): response = test_client.post(endpoint, json=valid_post_data) assert response.status_code == 200 diff --git a/backend/tests/controllers/test_cases/test_reported_issues.py b/backend/tests/controllers/test_cases/test_reported_issues.py index 693be2ab..e82d12ac 100644 --- a/backend/tests/controllers/test_cases/test_reported_issues.py +++ b/backend/tests/controllers/test_cases/test_reported_issues.py @@ -11,7 +11,7 @@ valid_post_data = { "template_id": "template 1", "case_name": "case", - "url": "http://issue.link/", + "url": "https://github.com/", "description": "some description", } @@ -81,6 +81,16 @@ def test_post_validates_url(post: Post): assert_fails_validation(response, "url", "url_parsing") +def test_url_cannot_be_canonical_chat(post: Post): + response = post( + { + **valid_post_data, + "url": "https://chat.canonical.com/canonical/pl/n7oahef13jdpde7p6nf7s5yisw", + } + ) + assert response.status_code == 422 + + def test_valid_template_id_post(post: Post): data = {k: v for k, v in valid_post_data.items() if k != "case_name"} response = post(data)