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
25 changes: 25 additions & 0 deletions Roost-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

# RoostGPT generated pytest code for API Testing

RoostGPT generats code in `tests` folder within given project path.
Dependency file i.e. `requirements-roost.txt` is also created in the given project path

Below are the sample steps to run the generated tests. Sample commands contains use of package manager i.e. `uv`. Alternatively python and pip can be used directly.
1. ( Optional ) Create virtual Env .
2. Install dependencies
```
uv venv // Create virtual Env
uv pip install -r requirements-roost.txt // Install all dependencies

```

Test configurations and test_data is loaded from config.yml. e.g. API HOST, auth, common path parameters of endpoint.
Either set defalt value in this config.yml file OR use ENV. e.g. export API_HOST="https://example.com/api/v2"

Once configuration values are set, use below commands to run the tests.
```
// Run generated tests
uv run pytest -m smoke // Run only smoke tests
uv run pytest -s tests/generated-test.py // Run specific test file
```

2 changes: 2 additions & 0 deletions requirements-roost.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

pytest
23,564 changes: 23,564 additions & 0 deletions tests/CIRCLECI_API/api.json

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions tests/CIRCLECI_API/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

# This config.yml contains user provided data for api testing. Allows to define values here or use ENV to load values. e.g. ENV[API_HOST] = "https://exampl2.com"
# api:
# host: "${API_HOST:-https://example.com/api/v2}" # includes base path
# auth:
# api_key: "${API_KEY:-}"
# api_key_header: "${KEYNAME:-DefaultValue}" # openapi.spec.security.KEY_NAME
# basic_auth: "${username:-}:${password:-}"
# test_data:
# id: "${TEST_ID:-282739-1238371-219393-2833}" # Any test data key value pair e.g. GET /api/v1/cart/:id
# context-id: "${TEST_context-id:-}" # GET /api/v1/{context-id}/summary



api:
host: "${API_HOST:-https://circleci.com/api/v2}"
auth:
api_key_header: "${Circle-Token:-}"
api_key_query: "${circle-token:-}"
test_data:
project-slug: "${TEST_project-slug:-}"
workflow-name: "${TEST_workflow-name:-}"
org-slug: "${TEST_org-slug:-}"
job-id: "${TEST_job-id:-}"
org-slug-or-id: "${TEST_org-slug-or-id:-}"
allow-list-entry-id: "${TEST_allow-list-entry-id:-}"
pipeline-id: "${TEST_pipeline-id:-}"
fingerprint: "${TEST_fingerprint:-}"
name: "${TEST_name:-}"
job-number: "${TEST_job-number:-}"
pipeline-number: "${TEST_pipeline-number:-}"
schedule-id: "${TEST_schedule-id:-}"
id: "${TEST_id:-}"
webhook-id: "${TEST_webhook-id:-}"
approval_request_id: "${TEST_approval_request_id:-}"
orgID: "${TEST_orgID:-}"
projectID: "${TEST_projectID:-}"
ownerID: "${TEST_ownerID:-}"
context: "${TEST_context:-}"
decisionID: "${TEST_decisionID:-}"
policyName: "${TEST_policyName:-}"
context_id: "${TEST_context_id:-}"
env_var_name: "${TEST_env_var_name:-}"
restriction_id: "${TEST_restriction_id:-}"
provider: "${TEST_provider:-}"
organization: "${TEST_organization:-}"
project: "${TEST_project:-}"
org_id: "${TEST_org_id:-}"
group_id: "${TEST_group_id:-}"
usage_export_job_id: "${TEST_usage_export_job_id:-}"
project_id: "${TEST_project_id:-}"
pipeline_definition_id: "${TEST_pipeline_definition_id:-}"
trigger_id: "${TEST_trigger_id:-}"
environment_id: "${TEST_environment_id:-}"
component_id: "${TEST_component_id:-}"
84 changes: 84 additions & 0 deletions tests/CIRCLECI_API/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import re
import pytest
import requests
import yaml
from pathlib import Path
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def _expand_env_in_string(value):
pattern = r'\${([^}:]+):-([^}]+)}'
def replacer(match):
env_var, default = match.groups()
return os.getenv(env_var, default)
return re.sub(pattern, replacer, value)

def load_config():
config_path = Path(__file__).parent / 'config.yml'
try:
with open(config_path, 'r') as file:
config = yaml.safe_load(file)
return {key: _expand_env_in_string(value) if isinstance(value, str) else value
for key, value in config.items()}
except FileNotFoundError:
raise RuntimeError(f"Configuration file not found at {config_path}")
except yaml.YAMLError as e:
raise RuntimeError(f"Error parsing YAML file: {e}")

@pytest.fixture(scope='session')
def config():
return load_config()

@pytest.fixture(scope='session')
def api_host(config):
return config['api']['host'].strip()

@pytest.fixture(scope='session')
def api_auth(config):
return {
'api_key_header': config['auth']['api_key_header'],
'api_key_query': config['auth']['api_key_query']
}

@pytest.fixture(scope='session')
def config_test_data(config):
return config['test_data']

class APIClient:
def __init__(self, host, auth):
self.host = host
self.auth = auth
self.session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[502, 503, 504])
self.session.mount('https://', HTTPAdapter(max_retries=retries))

def make_request(self, endpoint, method='GET', headers=None, **kwargs):
url = f"{self.host}/{endpoint.lstrip('/')}"
headers = headers or {}
headers.update({'Authorization': f"Bearer {self.auth['api_key_header']}"})
response = self.session.request(method, url, headers=headers, **kwargs)
response.raise_for_status()
return response

def get(self, endpoint, **kwargs):
return self.make_request(endpoint, 'GET', **kwargs)

def post(self, endpoint, **kwargs):
return self.make_request(endpoint, 'POST', **kwargs)

def put(self, endpoint, **kwargs):
return self.make_request(endpoint, 'PUT', **kwargs)

def delete(self, endpoint, **kwargs):
return self.make_request(endpoint, 'DELETE', **kwargs)

def patch(self, endpoint, **kwargs):
return self.make_request(endpoint, 'PATCH', **kwargs)

@pytest.fixture(scope='session')
def api_client(api_host, api_auth):
return APIClient(api_host, api_auth)

def pytest_configure(config):
config.addinivalue_line("markers", "smoke: mark test as smoke test")
50 changes: 50 additions & 0 deletions tests/CIRCLECI_API/context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[
{
"owner-id": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"owner-id": "123e4567-e89b-12d3-a456-426614174000",
"owner-slug": "valid-slug",
"owner-type": "organization",
"page-token": "token123",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"owner-slug": "valid-slug",
"owner-type": "organization",
"page-token": "token123",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"owner-id": "invalid-uuid",
"owner-slug": "valid-slug",
"owner-type": "organization",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"owner-id": "123e4567-e89b-12d3-a456-426614174000",
"owner-slug": "nonexistent-slug",
"owner-type": "organization",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
},
{
"owner-id": "123e4567-e89b-12d3-a456-426614174000",
"owner-slug": "valid-slug",
"owner-type": "organization",
"statusCode": 429,
"scenario": "Client error responses: Too Many Requests"
},
{
"owner-id": "123e4567-e89b-12d3-a456-426614174000",
"owner-slug": "valid-slug",
"owner-type": "organization",
"statusCode": 500,
"scenario": "Server error responses: Internal Server Error"
}
]
37 changes: 37 additions & 0 deletions tests/CIRCLECI_API/context_context_id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"context_id": "validContext123",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"context_id": "validContext123",
"statusCode": 401,
"scenario": "Client error responses: Unauthorized"
},
{
"context_id": "nonExistentContext",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
},
{
"context_id": "validContext123",
"statusCode": 429,
"scenario": "Client error responses: Too Many Requests"
},
{
"context_id": "validContext123",
"statusCode": 500,
"scenario": "Server error responses: Internal Server Error"
},
{
"context_id": "",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"context_id": "invalidContext!@#",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
}
]
38 changes: 38 additions & 0 deletions tests/CIRCLECI_API/context_context_id_environment-variable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"context_id": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"context_id": "123e4567-e89b-12d3-a456-426614174000",
"page-token": "abc123token",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"page-token": "abc123token",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"context_id": "invalid-uuid",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"context_id": "00000000-0000-0000-0000-000000000000",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
},
{
"context_id": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 429,
"scenario": "Client error responses: Too Many Requests"
},
{
"context_id": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 500,
"scenario": "Server error responses: Internal Server Error"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"context_id": "validContext123",
"env_var_name": "validEnvVar",
"statusCode": 200,
"scenario": "Successful responses: OK"
},
{
"context_id": "validContext123",
"env_var_name": "validEnvVar",
"statusCode": 401,
"scenario": "Client error responses: Unauthorized"
},
{
"context_id": "nonExistentContext",
"env_var_name": "validEnvVar",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
},
{
"context_id": "validContext123",
"env_var_name": "nonExistentEnvVar",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
},
{
"context_id": "validContext123",
"env_var_name": "validEnvVar",
"statusCode": 429,
"scenario": "Client error responses: Too Many Requests"
},
{
"context_id": "validContext123",
"env_var_name": "validEnvVar",
"statusCode": 500,
"scenario": "Server error responses: Internal Server Error"
}
]
31 changes: 31 additions & 0 deletions tests/CIRCLECI_API/context_context_id_restrictions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"restriction_type": "project",
"restriction_value": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 201,
"scenario": "Successful responses: Created"
},
{
"restriction_type": "project",
"restriction_value": "123e4567-e89b-12d3-a456-426614174000",
"statusCode": 201,
"scenario": "Successful responses: Created"
},
{
"restriction_type": "project",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"restriction_type": "invalid_type",
"restriction_value": "invalid_value",
"statusCode": 400,
"scenario": "Client error responses: Bad Request"
},
{
"restriction_type": "project",
"restriction_value": "non-existent-id",
"statusCode": 404,
"scenario": "Client error responses: Not Found"
}
]
Loading