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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "feature",
"description": "Added `SmithyKitchenSink` and `AwsKitchenSink` test clients for functional testing of smithy-python features."
}
1 change: 1 addition & 0 deletions packages/smithy-testing/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
87 changes: 87 additions & 0 deletions packages/smithy-testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# smithy-testing

This package provides generated test clients used to verify functionality in tools and libraries built with Smithy.


## Features
- **Generated Test Clients** - Real smithy-python clients for testing.
- **Test Fixtures** - Pytest fixtures for common testing scenarios.


## Test Clients

### 1. SmithyKitchenSink (Generic Smithy Client)
- **Purpose**: Test core smithy-python functionality (retries, request/response pipeline, serde)
- **Auth**: HTTP Basic Auth
- **Use Cases**: Core smithy functionality, protocol testing, generic client behavior

### 2. AwsKitchenSink (AWS Client)
- **Purpose**: Test AWS-specific functionality (SigV4 auth, credentials, endpoints)
- **Auth**: SigV4
- **Use Cases**: AWS authentication, credential resolution, AWS retry behavior


## Quick Start

### Basic Functional Test
```python
# In smithy-kitchen-sink/tests/

from smithy_core.retries import SimpleRetryStrategy
from smithy_http.testing import MockHTTPClient
from ..codegen.smithy_kitchen_sink.client import SmithyKitchenSink
from ..codegen.smithy_kitchen_sink.config import Config
from ..codegen.smithy_kitchen_sink.models import GetItemInput


async def test_simple_retry():
# Set up mock responses
http_client = MockHTTPClient()
http_client.add_response(
status=500,
headers=[("X-Amzn-Errortype", "InternalError")],
body=b'{"message": "server error"}'
)
http_client.add_response(
status=500,
headers=[("X-Amzn-Errortype", "InternalError")],
body=b'{"message": "server error"}'
)
http_client.add_response(200, body=b'{"message": "success"}')

# Create client with mock transport
config = Config(
transport=http_client,
endpoint_uri="https://test.example.com",
retry_strategy=SimpleRetryStrategy(max_attempts=3)
)
client = SmithyKitchenSink(config=config)

response = await client.get_item(GetItemInput(id="test-123"))

assert http_client.call_count == 3
assert response.message == "success"
```

## Development

### Regenerating Test Clients

After modifying Smithy models, regenerate the clients:
```
python src/smithy_testing/internal/generate_clients.py
```

### Test Organization
Tests live in each client's directory:
- `smithy-kitchen-sink/tests/`
- `aws-kitchen-sink/tests/`


### Adding New Test Scenarios

To add new operations or traits:
1. Edit the Smithy model files (`model/main.smithy`)
2. Update `smithy-build.json` if needed
3. Regenerate clients using the script above
4. Add tests using the new operations
52 changes: 52 additions & 0 deletions packages/smithy-testing/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[project]
name = "smithy-testing"
dynamic = ["version"]
requires-python = ">=3.12"
authors = [
{name = "Amazon Web Services"},
]
description = "Testing utilities and mock clients for smithy-python tests."
readme = "README.md"
license = {text = "Apache License 2.0"}
keywords = ["smithy", "sdk", "testing"]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"Natural Language :: English",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development :: Libraries"
]
dependencies = [
"smithy-core~=0.1.0",
"smithy-http~=0.2.0",
"smithy-aws-core~=0.1.0",
]

[project.urls]
"Changelog" = "https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-testing/CHANGELOG.md"
"Code" = "https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-testing/"
"Issue tracker" = "https://github.com/smithy-lang/smithy-python/issues"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/smithy_testing/__init__.py"

[tool.hatch.build]
exclude = [
"tests",
]

[tool.ruff]
src = ["src"]
4 changes: 4 additions & 0 deletions packages/smithy-testing/src/smithy_testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

__version__ = "0.0.1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Code generated by smithy-python-codegen DO NOT EDIT.

__version__: str = "0.0.1"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Code generated by smithy-python-codegen DO NOT EDIT.
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Code generated by smithy-python-codegen DO NOT EDIT.

from types import MappingProxyType

from smithy_core.prelude import STRING
from smithy_core.schemas import Schema
from smithy_core.shapes import ShapeID, ShapeType
from smithy_core.traits import Trait

CREATE_ITEM_INPUT = Schema.collection(
id=ShapeID("smithy.python.test#CreateItemInput"),
traits=[Trait.new(id=ShapeID("smithy.api#input"))],
members={
"name": {
"target": STRING,
"traits": [Trait.new(id=ShapeID("smithy.api#required"))],
}
},
)

CREATE_ITEM_OUTPUT = Schema.collection(
id=ShapeID("smithy.python.test#CreateItemOutput"),
traits=[Trait.new(id=ShapeID("smithy.api#output"))],
members={"id": {"target": STRING}, "name": {"target": STRING}},
)

CREATE_ITEM = Schema(
id=ShapeID("smithy.python.test#CreateItem"),
shape_type=ShapeType.OPERATION,
traits=[
Trait.new(
id=ShapeID("smithy.api#http"),
value=MappingProxyType({"code": 201, "method": "POST", "uri": "/items"}),
)
],
)

GET_ITEM_INPUT = Schema.collection(
id=ShapeID("smithy.python.test#GetItemInput"),
traits=[Trait.new(id=ShapeID("smithy.api#input"))],
members={
"id": {
"target": STRING,
"traits": [
Trait.new(id=ShapeID("smithy.api#required")),
Trait.new(id=ShapeID("smithy.api#httpLabel")),
],
}
},
)

GET_ITEM_OUTPUT = Schema.collection(
id=ShapeID("smithy.python.test#GetItemOutput"),
traits=[Trait.new(id=ShapeID("smithy.api#output"))],
members={"message": {"target": STRING}},
)

INTERNAL_ERROR = Schema.collection(
id=ShapeID("smithy.python.test#InternalError"),
traits=[
Trait.new(id=ShapeID("smithy.api#error"), value="server"),
Trait.new(id=ShapeID("smithy.api#httpError"), value=500),
Trait.new(id=ShapeID("smithy.api#retryable")),
],
members={"message": {"target": STRING}},
)

ITEM_NOT_FOUND = Schema.collection(
id=ShapeID("smithy.python.test#ItemNotFound"),
traits=[
Trait.new(id=ShapeID("smithy.api#error"), value="client"),
Trait.new(id=ShapeID("smithy.api#httpError"), value=400),
],
members={"message": {"target": STRING}},
)

SERVICE_UNAVAILABLE_ERROR = Schema.collection(
id=ShapeID("smithy.python.test#ServiceUnavailableError"),
traits=[
Trait.new(id=ShapeID("smithy.api#error"), value="server"),
Trait.new(id=ShapeID("smithy.api#httpError"), value=503),
Trait.new(id=ShapeID("smithy.api#retryable")),
],
members={"message": {"target": STRING}},
)

THROTTLING_ERROR = Schema.collection(
id=ShapeID("smithy.python.test#ThrottlingError"),
traits=[
Trait.new(id=ShapeID("smithy.api#error"), value="client"),
Trait.new(id=ShapeID("smithy.api#httpError"), value=429),
Trait.new(
id=ShapeID("smithy.api#retryable"),
value=MappingProxyType({"throttling": True}),
),
],
members={"message": {"target": STRING}},
)

GET_ITEM = Schema(
id=ShapeID("smithy.python.test#GetItem"),
shape_type=ShapeType.OPERATION,
traits=[
Trait.new(
id=ShapeID("smithy.api#http"),
value=MappingProxyType(
{"code": 200, "method": "GET", "uri": "/items/{id}"}
),
),
Trait.new(id=ShapeID("smithy.api#readonly")),
],
)

AWS_KITCHEN_SINK = Schema(
id=ShapeID("smithy.python.test#AwsKitchenSink"),
shape_type=ShapeType.SERVICE,
traits=[
Trait.new(
id=ShapeID("aws.auth#sigv4"),
value=MappingProxyType({"name": "awskitchensink"}),
),
Trait.new(id=ShapeID("aws.protocols#restJson1")),
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Code generated by smithy-python-codegen DO NOT EDIT.

from typing import Any

from smithy_core.auth import AuthOption, AuthParams
from smithy_core.interfaces.auth import AuthOption as AuthOptionProtocol
from smithy_core.shapes import ShapeID


class HTTPAuthSchemeResolver:
def resolve_auth_scheme(
self, auth_parameters: AuthParams[Any, Any]
) -> list[AuthOptionProtocol]:
auth_options: list[AuthOptionProtocol] = []

if (option := _generate_sigv4_option(auth_parameters)) is not None:
auth_options.append(option)

return auth_options


def _generate_sigv4_option(
auth_params: AuthParams[Any, Any],
) -> AuthOptionProtocol | None:
return AuthOption(
scheme_id=ShapeID("aws.auth#sigv4"),
identity_properties={}, # type: ignore
signer_properties={}, # type: ignore
)
Loading