Skip to content

Commit 4465f0c

Browse files
CRUD operation for Project of Python TFE
1 parent 6df4ddc commit 4465f0c

3 files changed

Lines changed: 57 additions & 75 deletions

File tree

examples/project_integration_test_example.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,11 @@ def test_list_projects_integration(integration_client):
9191

9292
if project_list:
9393
project = project_list[0]
94-
assert hasattr(project, 'id'), "Project should have an ID"
95-
assert hasattr(project, 'name'), "Project should have a name"
96-
assert hasattr(project, 'organization'), "Project should have an organization"
94+
assert hasattr(project, "id"), "Project should have an ID"
95+
assert hasattr(project, "name"), "Project should have a name"
96+
assert hasattr(project, "organization"), (
97+
"Project should have an organization"
98+
)
9799
print(f"📋 Example project: {project.name} (ID: {project.id})")
98100
else:
99101
print("📋 No projects found - this is normal for a new organization")
@@ -123,9 +125,15 @@ def test_project_crud_integration(integration_client):
123125
print(f"🔨 Creating project: {test_name}")
124126
created_project = projects.create(org, test_name)
125127

126-
assert created_project.name == test_name, f"Expected name {test_name}, got {created_project.name}"
127-
assert created_project.organization == org, f"Expected org {org}, got {created_project.organization}"
128-
assert created_project.id.startswith("prj-"), f"Project ID should start with 'prj-', got {created_project.id}"
128+
assert created_project.name == test_name, (
129+
f"Expected name {test_name}, got {created_project.name}"
130+
)
131+
assert created_project.organization == org, (
132+
f"Expected org {org}, got {created_project.organization}"
133+
)
134+
assert created_project.id.startswith("prj-"), (
135+
f"Project ID should start with 'prj-', got {created_project.id}"
136+
)
129137

130138
project_id = created_project.id
131139
print(f"✅ Created project: {project_id}")
@@ -134,16 +142,24 @@ def test_project_crud_integration(integration_client):
134142
print(f"📖 Reading project: {project_id}")
135143
read_project = projects.read(project_id)
136144

137-
assert read_project.id == project_id, f"Expected ID {project_id}, got {read_project.id}"
138-
assert read_project.name == test_name, f"Expected name {test_name}, got {read_project.name}"
145+
assert read_project.id == project_id, (
146+
f"Expected ID {project_id}, got {read_project.id}"
147+
)
148+
assert read_project.name == test_name, (
149+
f"Expected name {test_name}, got {read_project.name}"
150+
)
139151
print(f"✅ Successfully read project: {read_project.name}")
140152

141153
# UPDATE - Test updating the project name
142154
print(f"✏️ Updating project name to: {updated_name}")
143155
updated_project = projects.update(project_id, updated_name)
144156

145-
assert updated_project.id == project_id, f"Project ID should remain {project_id}"
146-
assert updated_project.name == updated_name, f"Expected updated name {updated_name}, got {updated_project.name}"
157+
assert updated_project.id == project_id, (
158+
f"Project ID should remain {project_id}"
159+
)
160+
assert updated_project.name == updated_name, (
161+
f"Expected updated name {updated_name}, got {updated_project.name}"
162+
)
147163
print(f"✅ Successfully updated project: {updated_project.name}")
148164

149165
except Exception as e:
@@ -158,7 +174,9 @@ def test_project_crud_integration(integration_client):
158174
print("✅ Test project deleted successfully")
159175
except Exception as e:
160176
print(f"❌ Warning: Failed to clean up project {project_id}: {e}")
161-
print(" You may need to manually delete this project in HCP Terraform")
177+
print(
178+
" You may need to manually delete this project in HCP Terraform"
179+
)
162180

163181

164182
def test_error_handling_integration(integration_client):
@@ -172,7 +190,9 @@ def test_error_handling_integration(integration_client):
172190
projects.read(fake_project_id)
173191
pytest.fail("Should have raised an exception for non-existent project")
174192
except Exception as e:
175-
print(f"✅ Correctly handled error for non-existent project: {type(e).__name__}")
193+
print(
194+
f"✅ Correctly handled error for non-existent project: {type(e).__name__}"
195+
)
176196
# This should raise a NotFound or similar error
177197
assert "not found" in str(e).lower() or "404" in str(e)
178198

@@ -197,7 +217,9 @@ def test_error_handling_integration(integration_client):
197217
sys.exit(1)
198218

199219
print("🧪 Running integration tests directly...")
200-
print(" For full pytest features, use: pytest examples/integration_test_example.py -v -s")
220+
print(
221+
" For full pytest features, use: pytest examples/integration_test_example.py -v -s"
222+
)
201223

202224
# Simple direct execution
203225
pytest.main([__file__, "-v", "-s"])

src/tfe/resources/projects.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,7 @@ def list(self, organization: str) -> Iterator[Project]:
2323
def create(self, organization: str, name: str) -> Project:
2424
"""Create a new project in an organization"""
2525
path = f"/api/v2/organizations/{organization}/projects"
26-
payload = {
27-
"data": {
28-
"type": "projects",
29-
"attributes": {
30-
"name": name
31-
}
32-
}
33-
}
26+
payload = {"data": {"type": "projects", "attributes": {"name": name}}}
3427

3528
# Use json_body parameter (correct parameter name)
3629
response = self.t.request("POST", path, json_body=payload)
@@ -40,7 +33,7 @@ def create(self, organization: str, name: str) -> Project:
4033
return Project(
4134
id=_safe_str(data.get("id")),
4235
name=_safe_str(attr.get("name")),
43-
organization=organization
36+
organization=organization,
4437
)
4538

4639
def read(self, project_id: str) -> Project:
@@ -58,20 +51,14 @@ def read(self, project_id: str) -> Project:
5851
return Project(
5952
id=_safe_str(data.get("id")),
6053
name=_safe_str(attr.get("name")),
61-
organization=organization
54+
organization=organization,
6255
)
6356

6457
def update(self, project_id: str, name: str) -> Project:
6558
"""Update a project's name"""
6659
path = f"/api/v2/projects/{project_id}"
6760
payload = {
68-
"data": {
69-
"type": "projects",
70-
"id": project_id,
71-
"attributes": {
72-
"name": name
73-
}
74-
}
61+
"data": {"type": "projects", "id": project_id, "attributes": {"name": name}}
7562
}
7663

7764
# Use json_body parameter (correct parameter name)
@@ -87,7 +74,7 @@ def update(self, project_id: str, name: str) -> Project:
8774
return Project(
8875
id=_safe_str(data.get("id")),
8976
name=_safe_str(attr.get("name")),
90-
organization=organization
77+
organization=organization,
9178
)
9279

9380
def delete(self, project_id: str) -> None:

tests/units/test_project.py

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,13 @@ def test_list_projects_success(self):
2525
{
2626
"id": "prj-123",
2727
"type": "projects",
28-
"attributes": {
29-
"name": "Test Project 1"
30-
}
28+
"attributes": {"name": "Test Project 1"},
3129
},
3230
{
3331
"id": "prj-456",
3432
"type": "projects",
35-
"attributes": {
36-
"name": "Test Project 2"
37-
}
38-
}
33+
"attributes": {"name": "Test Project 2"},
34+
},
3935
]
4036

4137
# Mock the _list method to return our test data
@@ -74,9 +70,7 @@ def test_create_project_success(self):
7470
"data": {
7571
"id": "prj-123",
7672
"type": "projects",
77-
"attributes": {
78-
"name": project_name
79-
}
73+
"attributes": {"name": project_name},
8074
}
8175
}
8276
self.mock_transport.request.return_value = mock_response
@@ -92,14 +86,11 @@ def test_create_project_success(self):
9286
# Verify API call
9387
expected_path = f"/api/v2/organizations/{organization}/projects"
9488
expected_payload = {
95-
"data": {
96-
"type": "projects",
97-
"attributes": {
98-
"name": project_name
99-
}
100-
}
89+
"data": {"type": "projects", "attributes": {"name": project_name}}
10190
}
102-
self.mock_transport.request.assert_called_once_with("POST", expected_path, json_body=expected_payload)
91+
self.mock_transport.request.assert_called_once_with(
92+
"POST", expected_path, json_body=expected_payload
93+
)
10394

10495
def test_read_project_success(self):
10596
"""Test successful project read"""
@@ -111,16 +102,8 @@ def test_read_project_success(self):
111102
"data": {
112103
"id": project_id,
113104
"type": "projects",
114-
"attributes": {
115-
"name": "Test Project"
116-
},
117-
"relationships": {
118-
"organization": {
119-
"data": {
120-
"id": "test-org"
121-
}
122-
}
123-
}
105+
"attributes": {"name": "Test Project"},
106+
"relationships": {"organization": {"data": {"id": "test-org"}}},
124107
}
125108
}
126109
self.mock_transport.request.return_value = mock_response
@@ -148,16 +131,8 @@ def test_update_project_success(self):
148131
"data": {
149132
"id": project_id,
150133
"type": "projects",
151-
"attributes": {
152-
"name": new_name
153-
},
154-
"relationships": {
155-
"organization": {
156-
"data": {
157-
"id": "test-org"
158-
}
159-
}
160-
}
134+
"attributes": {"name": new_name},
135+
"relationships": {"organization": {"data": {"id": "test-org"}}},
161136
}
162137
}
163138
self.mock_transport.request.return_value = mock_response
@@ -176,12 +151,12 @@ def test_update_project_success(self):
176151
"data": {
177152
"type": "projects",
178153
"id": project_id,
179-
"attributes": {
180-
"name": new_name
181-
}
154+
"attributes": {"name": new_name},
182155
}
183156
}
184-
self.mock_transport.request.assert_called_once_with("PATCH", expected_path, json_body=expected_payload)
157+
self.mock_transport.request.assert_called_once_with(
158+
"PATCH", expected_path, json_body=expected_payload
159+
)
185160

186161
def test_delete_project_success(self):
187162
"""Test successful project deletion"""
@@ -236,9 +211,7 @@ def test_read_project_missing_organization(self):
236211
"data": {
237212
"id": project_id,
238213
"type": "projects",
239-
"attributes": {
240-
"name": "Test Project"
241-
}
214+
"attributes": {"name": "Test Project"},
242215
# No relationships field
243216
}
244217
}

0 commit comments

Comments
 (0)