Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/pytfe/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class ProjectListOptions(BaseModel):

# Optional: String used to filter results by complete project name
name: str | None = None
# Optional: Tags used to filter results by key=value pairs
tags: dict[str, str] | None = None
# Optional: Query string to search projects by names
query: str | None = None
# Optional: Include related resources
Expand Down
4 changes: 4 additions & 0 deletions src/pytfe/resources/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ def list(
params["q"] = options.query
if options.name:
params["filter[names]"] = options.name
if options.tags:
for i, (tag_name, tag_value) in enumerate(options.tags.items()):
params[f"filter[tagged][{i}][key]"] = _safe_str(tag_name)
params[f"filter[tagged][{i}][value]"] = _safe_str(tag_value)
if options.page_size:
params["page[size]"] = options.page_size

Expand Down
46 changes: 46 additions & 0 deletions tests/units/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Project,
ProjectAddTagBindingsOptions,
ProjectCreateOptions,
ProjectListOptions,
ProjectUpdateOptions,
TagBinding,
)
Expand Down Expand Up @@ -109,6 +110,51 @@ def test_list_projects_success(self):
expected_path = f"/api/v2/organizations/{organization}/projects"
self.projects_service._list.assert_called_once_with(expected_path)

def test_list_projects_by_tags_query_params_serialized_success(self):
"""Test successful serialization of project list tag filter into query parameters."""
organization = "test-org"
expected_path = f"/api/v2/organizations/{organization}/projects"

# Mock the _list method so we can introspect the query parameters
self.projects_service._list = Mock(return_value=[])

# Call the method under test
list(
self.projects_service.list(
organization, options=ProjectListOptions(tags={"department": "five"})
)
)

self.projects_service._list.assert_called_once_with(
expected_path,
params={
"filter[tagged][0][key]": "department",
"filter[tagged][0][value]": "five",
},
)

def test_list_projects_by_name_query_params_serialized_success(self):
"""Test successful serialization of project list name filter into query parameters."""
organization = "test-org"
expected_path = f"/api/v2/organizations/{organization}/projects"

# Mock the _list method so we can introspect the query parameters
self.projects_service._list = Mock(return_value=[])

# Call the method under test
list(
self.projects_service.list(
organization, options=ProjectListOptions(name="prj-456")
)
)

self.projects_service._list.assert_called_once_with(
expected_path,
params={
"filter[names]": "prj-456",
},
)

def test_create_project_success(self):
"""Test successful project creation"""
organization = "test-org"
Expand Down
Loading