Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
128 changes: 128 additions & 0 deletions tests/units/test_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Tests for the endpoint module."""

import pytest
from requests import Session
from requests.models import Response as RequestResponse

from tfe.endpoint import Endpoint


@pytest.fixture
def mock_session(mocker):
"""Create a mock session for testing."""
return mocker.Mock(spec=Session)


@pytest.fixture
def endpoint(mock_session):
"""Create an Endpoint instance for testing."""
return Endpoint(client=mock_session)


@pytest.fixture
def mock_response(mocker):
"""Create a mock HTTP response."""
response = mocker.Mock(spec=RequestResponse)
response.status_code = 200
response.json.return_value = {"data": "test"}
return response


class TestEndpoint:
"""Test cases for the Endpoint class."""

@pytest.mark.parametrize(
"method,expected_method,json_data,expected_call",
[
("GET", "get", None, ("get", "/test/path")),
("POST", "post", {"key": "value"}, ("post", "/test/path")),
("PUT", "put", {"key": "value"}, ("put", "/test/path")),
("PATCH", "patch", {"key": "value"}, ("patch", "/test/path")),
("DELETE", "delete", None, ("delete", "/test/path")),
("get", "get", None, ("get", "/test/path")), # Test case insensitive
(
"post",
"post",
{"data": "test"},
("post", "/test/path"),
), # Test case insensitive
],
)
def test_make_request_all_methods(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again no test cases for error codes

self, endpoint, mock_response, method, expected_method, json_data, expected_call
):
"""Test _make_request with all supported HTTP methods."""
# Setup the mock method to return our mock response
getattr(endpoint._http_client, expected_method).return_value = mock_response

# Make the request
if json_data:
response = endpoint._make_request(method, "/test/path", json=json_data)
# Verify the correct method was called with the correct arguments
getattr(endpoint._http_client, expected_method).assert_called_once_with(
"/test/path", json=json_data
)
else:
response = endpoint._make_request(method, "/test/path")
# Verify the correct method was called with the correct arguments
getattr(endpoint._http_client, expected_method).assert_called_once_with(
"/test/path"
)

# Verify the response is returned correctly
assert response == mock_response

def test_make_request_unsupported_method(self, endpoint):
"""Test that unsupported HTTP methods raise ValueError."""
with pytest.raises(ValueError, match="Unsupported HTTP method: INVALID"):
endpoint._make_request("INVALID", "/test/path")

def test_get_method(self, endpoint, mock_response):
"""Test the _get convenience method."""
endpoint._http_client.get.return_value = mock_response

response = endpoint._get("/test/path")

endpoint._http_client.get.assert_called_once_with("/test/path")
assert response == mock_response

def test_post_method(self, endpoint, mock_response):
"""Test the _post convenience method."""
endpoint._http_client.post.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._post("/test/path", test_data)

endpoint._http_client.post.assert_called_once_with("/test/path", json=test_data)
assert response == mock_response

def test_put_method(self, endpoint, mock_response):
"""Test the _put convenience method."""
endpoint._http_client.put.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._put("/test/path", test_data)

endpoint._http_client.put.assert_called_once_with("/test/path", json=test_data)
assert response == mock_response

def test_patch_method(self, endpoint, mock_response):
"""Test the _patch convenience method."""
endpoint._http_client.patch.return_value = mock_response
test_data = {"key": "value"}

response = endpoint._patch("/test/path", test_data)

endpoint._http_client.patch.assert_called_once_with(
"/test/path", json=test_data
)
assert response == mock_response

def test_delete_method(self, endpoint, mock_response):
"""Test the _delete convenience method."""
endpoint._http_client.delete.return_value = mock_response

response = endpoint._delete("/test/path")

endpoint._http_client.delete.assert_called_once_with("/test/path")
assert response == mock_response
12 changes: 0 additions & 12 deletions tfe/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +0,0 @@
"""
Python client library for Terraform Enterprise/Cloud API.

This package provides a Python interface to the Terraform Enterprise
and Terraform Cloud APIs, allowing you to programmatically manage
workspaces, runs, state files, and other TFE/TFC resources.
"""

from tfe.client import Client, TFEClientError
from tfe.config import Config

__all__ = ["Client", "TFEClientError", "Config"]
78 changes: 78 additions & 0 deletions tfe/endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Base service class for Terraform Enterprise/Cloud API services.

This module provides an abstract base class that all TFE API services
should inherit from. It provides common functionality for HTTP requests
and defines the interface that all service implementations must follow.
"""

import logging
from abc import ABC, abstractmethod
from typing import Any, TypeVar

from requests import Session
from requests.models import Response as RequestResponse

logger = logging.getLogger(__name__)

T = TypeVar("T", bound="Response")


class Response(ABC):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to fully implement this? Currently there is no implementation or any sort of relation with endpoint class.

@classmethod
@abstractmethod
def from_http_response(cls: type[T], response: RequestResponse) -> T:
"""Create an instance of the endpoint specific response class from an HTTP response."""
Comment thread
taru-garg-2000 marked this conversation as resolved.
Outdated
pass


class Endpoint:
"""Base class for all TFE API services."""

def __init__(self, client: Session) -> None:
self._http_client = client

def _make_request(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we checking for response codes / raising any errors for those HTTP errors?

self, method: str, path: str, json: dict[str, Any] | None = None
) -> RequestResponse:
"""
Make an HTTP request using the client's HTTP client.

Args:
method: HTTP method (GET, POST, PUT, PATCH, DELETE)
path: API path
json: JSON payload for POST, PUT, PATCH requests
Returns:
The HTTP response from requests library
"""
method = method.upper()

# Make the request
match method:
case "GET":
return self._http_client.get(path)
case "POST":
return self._http_client.post(path, json=json)
case "PUT":
return self._http_client.put(path, json=json)
case "PATCH":
return self._http_client.patch(path, json=json)
case "DELETE":
return self._http_client.delete(path)
case _:
raise ValueError(f"Unsupported HTTP method: {method}")

def _get(self, path: str) -> RequestResponse:
return self._make_request("GET", path)

def _post(self, path: str, data: dict) -> RequestResponse:
return self._make_request("POST", path, json=data)

def _put(self, path: str, data: dict) -> RequestResponse:
return self._make_request("PUT", path, json=data)

def _patch(self, path: str, data: dict) -> RequestResponse:
return self._make_request("PATCH", path, json=data)

def _delete(self, path: str) -> RequestResponse:
return self._make_request("DELETE", path)
Loading