Skip to content

feat: add test setup with cloud-tasks-emulator #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
866184e
feat: add test setup with cloud-tasks-emulator
devin-ai-integration[bot] Feb 8, 2025
ed105d2
chore: update poetry.lock
devin-ai-integration[bot] Feb 8, 2025
b78b0c3
style: fix whitespace in test files
devin-ai-integration[bot] Feb 8, 2025
5aa1fd7
test: add comprehensive test coverage
devin-ai-integration[bot] Feb 8, 2025
adc33a6
style: fix docstring and linting issues
devin-ai-integration[bot] Feb 8, 2025
5edb1a0
test: add specific match parameters to pytest.raises assertions
devin-ai-integration[bot] Feb 8, 2025
5d6dd67
ci: update workflow to target master branch
devin-ai-integration[bot] Feb 8, 2025
922e418
fix: filter queue parameter from location_path args
devin-ai-integration[bot] Feb 8, 2025
05dfb12
style: format utils.py
devin-ai-integration[bot] Feb 8, 2025
7da0787
fix: update emulator client to use correct gRPC endpoint
devin-ai-integration[bot] Feb 8, 2025
d6182cf
fix: update emulator configuration to use consistent environment vari…
devin-ai-integration[bot] Feb 8, 2025
37fade8
fix: update emulator client usage in examples
devin-ai-integration[bot] Feb 8, 2025
2477cd3
fix: simplify emulator configuration to use single port
devin-ai-integration[bot] Feb 8, 2025
ab09524
fix: update docker configuration to match emulator port setup
devin-ai-integration[bot] Feb 8, 2025
38a5924
fix: add socat to emulator container and update port configuration
devin-ai-integration[bot] Feb 8, 2025
f9b9013
fix: update emulator configuration to bind gRPC port to all interfaces
devin-ai-integration[bot] Feb 8, 2025
b1ce29d
fix: remove unused import and update emulator port configuration
devin-ai-integration[bot] Feb 8, 2025
21a5792
fix: bind emulator to all interfaces for both HTTP and gRPC
devin-ai-integration[bot] Feb 8, 2025
b92df9f
fix: simplify emulator configuration to use single port for both HTTP…
devin-ai-integration[bot] Feb 8, 2025
8bb1717
fix: remove unused port mapping and ensure consistent port configuration
devin-ai-integration[bot] Feb 8, 2025
77e9145
fix: ensure both HTTP and gRPC interfaces bind to all network interfaces
devin-ai-integration[bot] Feb 8, 2025
12d556c
fix: update emulator connection configuration in docker-compose.yml
devin-ai-integration[bot] Feb 8, 2025
21476c5
fix: update emulator client to handle host and port configuration sep…
devin-ai-integration[bot] Feb 8, 2025
a77fdcd
fix: enable insecure gRPC connections for emulator
devin-ai-integration[bot] Feb 8, 2025
b2731da
fix: update environment variables for emulator connection
devin-ai-integration[bot] Feb 8, 2025
19bba44
fix: add grpc_bind_all flag to ensure emulator binds to all interfaces
devin-ai-integration[bot] Feb 8, 2025
f631db4
fix: separate host and port configuration for emulator connection
devin-ai-integration[bot] Feb 8, 2025
ca02c60
fix: disable HTTP proxy and retries in emulator client
devin-ai-integration[bot] Feb 8, 2025
83e7113
fix: add bind_all flag to ensure emulator binds to all interfaces
devin-ai-integration[bot] Feb 8, 2025
909938a
fix: update emulator command to bind to all interfaces
devin-ai-integration[bot] Feb 8, 2025
744cd23
fix: add additional gRPC channel options for connection stability
devin-ai-integration[bot] Feb 8, 2025
59ff6b8
fix: update network configuration and add UDP port mapping
devin-ai-integration[bot] Feb 8, 2025
ee8c2f0
fix: add DNS resolution configuration for Docker networking
devin-ai-integration[bot] Feb 8, 2025
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
57 changes: 57 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest

services:
cloud-tasks-emulator:
image: ghcr.io/aertje/cloud-tasks-emulator:latest
ports:
- 8123:8123

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: "1.8.2"

- name: Install dependencies
run: |
poetry install --with test
poetry install --with dev

- name: Run linting
run: poetry run sh scripts/lint.sh

- name: Run type checking
run: poetry run mypy fastapi_gcp_tasks

- name: Run tests
run: poetry run pytest --cov=fastapi_gcp_tasks --cov-report=xml
env:
IS_LOCAL: "true"
CLOUD_TASKS_EMULATOR_URL: http://localhost:8123
TASK_LISTENER_BASE_URL: http://localhost:8000
TASK_PROJECT_ID: test-project
TASK_LOCATION: us-central1
TASK_QUEUE: test-queue

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: true
2 changes: 2 additions & 0 deletions Dockerfile.emulator
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM ghcr.io/aertje/cloud-tasks-emulator:latest
RUN apk add --no-cache wget socat
38 changes: 38 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: '3'
services:
cloud-tasks-emulator:
build:
context: .
dockerfile: Dockerfile.emulator
command: sh -c "/emulator -host 0.0.0.0 -port 8123 -grpc_port 8123 -grpc_host 0.0.0.0 -grpc_insecure -grpc_bind_all -bind_all"
ports:
- "8123:8123"
- "8123:8123/udp"
network_mode: "bridge"

healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://0.0.0.0:8123/v1/projects/test-project/locations/us-central1/queues"]
interval: 1s
timeout: 3s
retries: 30
tests:
image: python:3.11
depends_on:
cloud-tasks-emulator:
condition: service_healthy
volumes:
- .:/app
working_dir: /app
environment:
- IS_LOCAL=true
- CLOUD_TASKS_EMULATOR_HOST=cloud-tasks-emulator
- CLOUD_TASKS_EMULATOR_PORT=8123
- CLOUD_TASKS_EMULATOR_URL=http://cloud-tasks-emulator:8123
- TASK_LISTENER_BASE_URL=http://localhost:8000
- TASK_PROJECT_ID=test-project
- TASK_LOCATION=us-central1
- TASK_QUEUE=test-queue
command: >
bash -c "pip install poetry &&
poetry install &&
poetry run pytest tests/ -v"
112 changes: 112 additions & 0 deletions docs/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Testing Guide

## Prerequisites
- Python 3.11 or higher
- Poetry for dependency management
- cloud-tasks-emulator (install from https://github.com/aertje/cloud-tasks-emulator)

## Environment Variables
Required environment variables for testing:
```bash
# Local development flag
IS_LOCAL=true

# Task emulator settings
CLOUD_TASKS_EMULATOR_URL=http://localhost:8123
TASK_LISTENER_BASE_URL=http://localhost:8000

# Task queue settings
TASK_PROJECT_ID=test-project
TASK_LOCATION=us-central1
TASK_QUEUE=test-queue
```

## Running Tests

1. Install dependencies:
```bash
poetry install --with test
```

2. Start the cloud-tasks-emulator:
```bash
cloud-tasks-emulator
```

3. Run tests:
```bash
# Run all tests
poetry run pytest

# Run with coverage report
poetry run pytest --cov=fastapi_gcp_tasks

# Run specific test file
poetry run pytest tests/test_delayed_route.py
```

## Test Structure

The test suite is organized into several key areas:

### Core Functionality Tests
- DelayedRouteBuilder
* Basic task creation and execution
* Queue auto-creation
* Task options (countdown, task_id)
* Error handling and validation

- ScheduledRouteBuilder
* Basic job creation
* Cron schedule validation
* Time zone handling
* Job updates and uniqueness

### Hook and Dependency Tests
- Task Hooks
* OIDC token hooks
* Deadline hooks
* Chained hooks
* Custom hook creation
- Dependencies
* max_retries functionality
* CloudTasksHeaders integration
* Error propagation

### Example Implementation Tests
- Simple Example
* Local mode functionality
* Task queueing
* Environment variable handling
* Default settings

- Full Example
* Chained hook configuration
* Retry mechanisms
* Scheduled tasks
* Environment-specific behavior

### Environment-Specific Tests
- Local Development
* Emulator integration
* Default configurations
* Environment variable handling

- Deployed Environment
* OIDC token integration
* Cloud Scheduler integration
* Job scheduling
* Production settings

## Contributing Tests

When adding new tests:
1. Follow existing test patterns
2. Ensure proper type hints are used
3. Run formatting and linting:
```bash
sh scripts/format.sh
sh scripts/lint.sh
```
4. Add appropriate docstrings for complex test cases
5. Consider edge cases and error scenarios
2 changes: 1 addition & 1 deletion examples/full/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
SCHEDULED_LOCATION = os.getenv("SCHEDULED_LOCATION", default="us-central1")
TASK_QUEUE = os.getenv("TASK_QUEUE", default="test-queue")

CLOUD_TASKS_EMULATOR_URL = os.getenv("CLOUD_TASKS_EMULATOR_URL", "localhost:8123")
CLOUD_TASKS_EMULATOR_HOST = os.getenv("CLOUD_TASKS_EMULATOR_HOST", "localhost:8124")

TASK_SERVICE_ACCOUNT = os.getenv(
"TASK_SERVICE_ACCOUNT",
Expand Down
3 changes: 1 addition & 2 deletions examples/full/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
# Imports from this repository
from examples.full.serializer import Payload
from examples.full.settings import (
CLOUD_TASKS_EMULATOR_URL,
IS_LOCAL,
SCHEDULED_LOCATION_PATH,
SCHEDULED_OIDC_TOKEN,
Expand All @@ -36,7 +35,7 @@

delayed_client = None
if IS_LOCAL:
delayed_client = emulator_client(host=CLOUD_TASKS_EMULATOR_URL)
delayed_client = emulator_client()

DelayedRoute = DelayedRouteBuilder(
client=delayed_client,
Expand Down
30 changes: 26 additions & 4 deletions fastapi_gcp_tasks/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Third Party Imports
# Standard Library Imports
import os
from typing import Any

# Third Party Imports
import grpc
from google.api_core.exceptions import AlreadyExists
from google.cloud import scheduler_v1, tasks_v2
Expand Down Expand Up @@ -31,8 +33,9 @@ def ensure_queue(
"""
# We extract information from the queue path to make the public api simpler
parsed_queue_path = client.parse_queue_path(path=path)
location_args = {k: v for k, v in parsed_queue_path.items() if k in ("project", "location")}
create_req = tasks_v2.CreateQueueRequest(
parent=location_path(**parsed_queue_path),
parent=location_path(**location_args),
queue=tasks_v2.Queue(name=path, **kwargs),
)
try:
Expand All @@ -41,8 +44,27 @@ def ensure_queue(
pass


def emulator_client(*, host: str = "localhost:8123") -> tasks_v2.CloudTasksClient:
def emulator_client() -> tasks_v2.CloudTasksClient:
"""Helper function to create a CloudTasksClient from an emulator host."""
channel = grpc.insecure_channel(host)
host = os.getenv("CLOUD_TASKS_EMULATOR_HOST", "localhost")
port = os.getenv("CLOUD_TASKS_EMULATOR_PORT", "8123")
target = f"{host}:{port}"

# Configure DNS resolution for Docker networking
options = [
('grpc.enable_http_proxy', 0),
('grpc.enable_retries', 0),
('grpc.max_receive_message_length', -1),
('grpc.max_send_message_length', -1),
('grpc.keepalive_time_ms', 30000),
('grpc.dns_resolver_query_timeout_ms', 1000),
('grpc.dns_resolver_backoff_multiplier', 1.0),
('grpc.dns_resolver_backoff_jitter', 0.0),
('grpc.dns_resolver_backoff_min_seconds', 1),
('grpc.dns_resolver_backoff_max_seconds', 5),
]

# Create channel with DNS resolution options
channel = grpc.insecure_channel(target, options=options)
transport = transports.CloudTasksGrpcTransport(channel=channel)
return tasks_v2.CloudTasksClient(transport=transport)
Loading
Loading