Skip to content

Commit fce4dac

Browse files
committed
Adding testcontainer - DRAFT
1 parent a8d3344 commit fce4dac

File tree

5 files changed

+76
-99
lines changed

5 files changed

+76
-99
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ COPY docker-entrypoint.sh docker-entrypoint.sh
2121

2222
# COPY request-api/makefile makefile
2323

24-
ENTRYPOINT ["./docker-entrypoint.sh"]
24+
CMD ["pytest"]

docker-compose.yml

+18-18
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,24 @@ services:
4848
- 10.0.2.20
4949
networks:
5050
- ls
51-
test:
52-
build:
53-
context: .
54-
dockerfile: Dockerfile
55-
environment:
56-
TEST_ENV: "true"
57-
AWS_ACCESS_KEY_ID: testing
58-
AWS_SECRET_ACCESS_KEY: testing
59-
AWS_DEFAULT_REGION: us-west-2
60-
AWS_ENDPOINT_URL: http://localstack:4566
61-
COLLECTION_BUCKET: test-bucket
62-
LOGGING_LEVEL: DEBUG
63-
USE_AWS_CREDENTIAL_CHAIN: "false"
64-
command: ["pytest", "tests/"]
65-
depends_on:
66-
- localstack
67-
networks:
68-
- ls
51+
# test:
52+
# build:
53+
# context: .
54+
# dockerfile: Dockerfile
55+
# environment:
56+
# TEST_ENV: "true"
57+
# AWS_ACCESS_KEY_ID: testing
58+
# AWS_SECRET_ACCESS_KEY: testing
59+
# AWS_DEFAULT_REGION: us-west-2
60+
# AWS_ENDPOINT_URL: http://localstack:4566
61+
# COLLECTION_BUCKET: test-bucket
62+
# LOGGING_LEVEL: DEBUG
63+
# USE_AWS_CREDENTIAL_CHAIN: "false"
64+
# command: ["pytest", "tests/"]
65+
# depends_on:
66+
# - localstack
67+
# networks:
68+
# - ls
6969
networks:
7070
# localstack network is used so that requests to localstack resolve from containers
7171
# See https://docs.localstack.cloud/references/network-troubleshooting/endpoint-url/

requirements/test_requirements.txt

+16-4
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ anyio==4.6.2.post1
1414
# httpx
1515
# starlette
1616
# watchfiles
17-
boto3==1.35.73
18-
# via moto
19-
botocore==1.35.73
17+
boto3==1.35.74
18+
# via
19+
# moto
20+
# testcontainers
21+
botocore==1.35.74
2022
# via
2123
# boto3
2224
# moto
@@ -42,6 +44,8 @@ dnspython==2.7.0
4244
# via
4345
# -r /home/ssadhu/pipeline-internal-api/requirements/requirements.txt
4446
# email-validator
47+
docker==7.1.0
48+
# via testcontainers
4549
duckdb==1.1.3
4650
# via -r /home/ssadhu/pipeline-internal-api/requirements/requirements.txt
4751
email-validator==2.2.0
@@ -153,6 +157,7 @@ pyyaml==6.0.2
153157
# uvicorn
154158
requests==2.32.3
155159
# via
160+
# docker
156161
# moto
157162
# responses
158163
responses==0.25.3
@@ -178,6 +183,8 @@ starlette==0.41.2
178183
# via
179184
# -r /home/ssadhu/pipeline-internal-api/requirements/requirements.txt
180185
# fastapi
186+
testcontainers[localstack]==4.8.2
187+
# via -r requirements/test_requirements.in
181188
tomli==2.2.1
182189
# via pytest
183190
typer==0.13.0
@@ -192,13 +199,16 @@ typing-extensions==4.12.2
192199
# pydantic
193200
# pydantic-core
194201
# rich
202+
# testcontainers
195203
# typer
196204
# uvicorn
197-
urllib3==1.26.18
205+
urllib3==2.2.3
198206
# via
199207
# botocore
208+
# docker
200209
# requests
201210
# responses
211+
# testcontainers
202212
uvicorn[standard]==0.32.0
203213
# via
204214
# -r /home/ssadhu/pipeline-internal-api/requirements/requirements.txt
@@ -218,5 +228,7 @@ websockets==13.1
218228
# uvicorn
219229
werkzeug==3.1.3
220230
# via moto
231+
wrapt==1.17.0
232+
# via testcontainers
221233
xmltodict==0.14.2
222234
# via moto

src/main.py

-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ async def validation_exception_handler(request: Request, exc: ValidationError):
3939

4040
@app.get("/log/issue", tags=["Issue"])
4141
def issues(http_response: Response, params: IssuesParams = Depends()):
42-
logger.debug("params: ",params.dataset)
4342

4443
paginated_result = db.search_issues(params)
4544
http_response.headers["X-Pagination-Total-Results"] = str(

tests/conftest.py

+41-75
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,64 @@
1+
import os
12
import pytest
23
import duckdb
3-
import os
44
import boto3
5-
from moto import mock_aws
5+
from testcontainers.localstack import LocalStackContainer
66
from botocore.exceptions import ClientError
77

88

9-
10-
11-
os.environ["AWS_DEFAULT_REGION"] = "eu-west-2"
12-
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
13-
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
14-
os.environ["AWS_SECURITY_TOKEN"] = "testing"
15-
os.environ["AWS_SESSION_TOKEN"] = "testing"
9+
os.environ["AWS_ACCESS_KEY_ID"] = "test"
10+
os.environ["AWS_SECRET_ACCESS_KEY"] = "test"
11+
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
1612
os.environ["COLLECTION_BUCKET"] = "test-bucket"
1713
os.environ["ISSUES_BASE_PATH"] = "test/path"
18-
os.environ["USE_AWS_CREDENTIAL_CHAIN"] = "false"
19-
20-
21-
2214

2315
@pytest.fixture(scope="module")
24-
def duckdb_connection():
25-
"""
26-
Fixture to provide a DuckDB in-memory database connection.
27-
"""
28-
conn = duckdb.connect(":memory:") # In-memory database for testing
29-
conn.execute(f"SET s3_endpoint = 'localstack:4566';")
30-
conn.execute(f"SET s3_access_key_id = '{os.getenv('AWS_ACCESS_KEY_ID')}';")
31-
conn.execute(f"SET s3_secret_access_key = '{os.getenv('AWS_SECRET_ACCESS_KEY')}';")
32-
conn.execute(f"SET s3_region = '{os.getenv('AWS_DEFAULT_REGION')}';")
33-
conn.execute("SET s3_use_ssl=false;")
34-
conn.execute("SET s3_url_style = 'path';")
35-
yield conn
36-
conn.close()
37-
38-
16+
def localstack_container():
17+
# Start LocalStack container
18+
with LocalStackContainer(image="localstack/localstack:2.0.1") as localstack:
19+
# Wait for the service to be ready
20+
yield localstack
3921

4022
@pytest.fixture(scope="module")
41-
def s3_client():
42-
"""
43-
Fixture to provide an S3 client connected to LocalStack.
44-
"""
45-
# Create an S3 client with the LocalStack endpoint
23+
def s3_client(localstack_container):
24+
# Create an S3 client using the LocalStack endpoint
4625
s3 = boto3.client(
4726
"s3",
48-
endpoint_url="http://localstack:4566", # LocalStack S3 endpoint
27+
endpoint_url=localstack_container.get_url(),
4928
region_name=os.environ["AWS_DEFAULT_REGION"],
50-
aws_access_key_id=os.getenv('AWS_ACCESS_KEY_ID'),
51-
aws_secret_access_key=os.getenv('AWS_SECRET_ACCESS_KEY'),
29+
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
30+
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"]
5231
)
5332
return s3
5433

34+
@pytest.fixture(scope="module")
35+
def duckdb_connection(localstack_container):
36+
37+
with LocalStackContainer() as localstack:
38+
localstack_hostname = localstack.get_container_host_ip()
39+
# Set up a DuckDB in-memory database
40+
conn = duckdb.connect(":memory:")
41+
# Configure DuckDB to connect to S3 via LocalStack
42+
conn.execute(f"SET s3_endpoint = '{localstack_hostname}:{4566}';")
43+
conn.execute(f"SET s3_access_key_id = '{os.getenv('AWS_ACCESS_KEY_ID')}';")
44+
conn.execute(f"SET s3_secret_access_key = '{os.getenv('AWS_SECRET_ACCESS_KEY')}';")
45+
conn.execute(f"SET s3_region = '{os.getenv('AWS_DEFAULT_REGION')}';")
46+
conn.execute("SET s3_use_ssl = FALSE;")
47+
yield conn
48+
conn.close()
5549

5650
@pytest.fixture(scope="module")
5751
def s3_bucket(s3_client):
58-
"""
59-
Fixture to set up a test bucket and upload a Parquet file in LocalStack.
60-
"""
52+
# Create a bucket in LocalStack for the test
6153
bucket_name = os.environ["COLLECTION_BUCKET"]
62-
parquet_file = "tests/files/issues.parquet"
63-
64-
# Check if the bucket exists
65-
existing_buckets = s3_client.list_buckets().get("Buckets", [])
66-
if not any(bucket["Name"] == bucket_name for bucket in existing_buckets):
67-
# Create the bucket if it doesn't exist
68-
s3_client.create_bucket(
69-
Bucket=bucket_name,
70-
CreateBucketConfiguration={"LocationConstraint": os.environ["AWS_DEFAULT_REGION"]},
71-
)
72-
73-
# Delete any pre-existing objects in the bucket
74-
objects = s3_client.list_objects_v2(Bucket=bucket_name).get("Contents", [])
75-
for obj in objects:
76-
s3_client.delete_object(Bucket=bucket_name, Key=obj["Key"])
77-
78-
# Upload the Parquet file
79-
with open(parquet_file, "rb") as file:
80-
s3_client.put_object(
81-
Bucket=bucket_name,
82-
Key=f"{os.environ['ISSUES_BASE_PATH']}/issues.parquet",
83-
Body=file,
84-
)
85-
54+
try:
55+
s3_client.create_bucket(Bucket=bucket_name)
56+
except ClientError:
57+
pass # Ignore if bucket already exists
58+
59+
# Upload a Parquet file to the bucket
60+
parquet_file = "tests/files/issues.parquet"
61+
with open(parquet_file, "rb") as f:
62+
s3_client.put_object(Bucket=bucket_name, Key="test/path/issues.parquet", Body=f)
63+
8664
yield s3_client
87-
88-
@pytest.fixture
89-
def s3_uri():
90-
"""
91-
Fixture that provides the s3 URI for use in tests.
92-
"""
93-
bucket_name = os.environ["COLLECTION_BUCKET"]
94-
base_path = os.environ["ISSUES_BASE_PATH"]
95-
s3_uri = f"s3://{bucket_name}/{base_path}/**/*.parquet"
96-
return s3_uri
97-
98-

0 commit comments

Comments
 (0)