Skip to content

Commit b729cd2

Browse files
authored
Delete testing RDS instance for cost efficiency (#32)
1 parent adc181b commit b729cd2

File tree

4 files changed

+72
-40
lines changed

4 files changed

+72
-40
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Run them with `poetry run pytest`.
8282

8383
Note that tests marked with `aws` are skipped by default, to avoid the need for an AWS setup.
8484
They are however ran in the GitHub Action.
85-
For this to work, they must have been ran once locally with an account with sufficient permissions (`poetry run pytest -m "aws"`), since for security reasons, the AWS account used on GitHub does not have permissions to create RDS instances.
85+
You can run them locally by adding `-m 'aws or not(aws)'` to the `pytest` command.
8686

8787
### Add/modify runnable applications
8888
#### Dockerize the application

tests/conftest.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
REGION_NAME: BucketLocationConstraintType = "eu-central-1"
1919

2020

21-
@pytest.fixture(scope="module")
21+
@pytest.fixture(scope="session")
2222
def monkeypatch_module() -> Generator[pytest.MonkeyPatch, Any, None]:
2323
with pytest.MonkeyPatch.context() as mp:
2424
yield mp
@@ -38,14 +38,16 @@ def enqueueing_func(monkeypatch_module: pytest.MonkeyPatch) -> MagicMock:
3838
class RDSTestingInstance:
3939
def __init__(self, db_name: str):
4040
self.db_name = db_name
41+
42+
def create(self) -> None:
4143
self.rds_client = boto3.client("rds", REGION_NAME)
4244
self.ec2_client = boto3.client("ec2", REGION_NAME)
4345
self.add_ingress_rule()
4446
self.db_url = self.create_db_url()
47+
self.engine = self.get_engine()
4548
self.delete_db_tables()
4649

47-
@property
48-
def engine(self) -> Engine:
50+
def get_engine(self) -> Engine:
4951
for _ in range(5):
5052
try:
5153
engine = create_engine(self.db_url)
@@ -117,12 +119,14 @@ def create_db_url(self) -> str:
117119
DBName=self.db_name,
118120
DBInstanceIdentifier=self.db_name,
119121
AllocatedStorage=20,
120-
DBInstanceClass="db.t3.micro",
122+
DBInstanceClass="db.t4g.micro",
121123
Engine="postgres",
122124
MasterUsername=user,
123125
MasterUserPassword=password,
124126
DeletionProtection=False,
125127
BackupRetentionPeriod=0,
128+
MultiAZ=False,
129+
EnablePerformanceInsights=False,
126130
)
127131
break
128132
except self.rds_client.exceptions.DBInstanceAlreadyExistsFault:
@@ -143,19 +147,34 @@ def cleanup(self) -> None:
143147
self.delete_db_tables()
144148
self.ec2_client.revoke_security_group_ingress(**self.vpc_sg_rule_params)
145149

150+
def delete(self) -> None:
151+
self.rds_client.delete_db_instance(
152+
DBInstanceIdentifier=self.db_name,
153+
SkipFinalSnapshot=True,
154+
DeleteAutomatedBackups=True,
155+
)
156+
146157

147158
class S3TestingBucket:
148159
def __init__(self, bucket_name_suffix: str):
149160
# S3 bucket names must be globally unique - avoid collisions by adding suffix
150161
self.bucket_name = f"{TEST_BUCKET_PREFIX}-{bucket_name_suffix}"
151162
self.region_name: BucketLocationConstraintType = REGION_NAME
163+
164+
def create(self) -> None:
152165
self.s3_client = boto3.client(
153166
"s3",
154167
region_name=self.region_name,
155168
# required for pre-signing URLs to work
156169
endpoint_url=f"https://s3.{self.region_name}.amazonaws.com",
157170
)
158-
self.initialize_bucket()
171+
exists = self.cleanup()
172+
if not exists:
173+
self.s3_client.create_bucket(
174+
Bucket=self.bucket_name,
175+
CreateBucketConfiguration={"LocationConstraint": self.region_name},
176+
)
177+
self.s3_client.get_waiter("bucket_exists").wait(Bucket=self.bucket_name)
159178

160179
def cleanup(self) -> bool:
161180
"""Returns True if bucket exists and all objects are deleted."""
@@ -174,19 +193,21 @@ def cleanup(self) -> bool:
174193
s3_bucket.objects.all().delete()
175194
return True
176195

177-
def initialize_bucket(self) -> None:
196+
def delete(self) -> None:
178197
exists = self.cleanup()
179-
if not exists:
180-
self.s3_client.create_bucket(
181-
Bucket=self.bucket_name,
182-
CreateBucketConfiguration={"LocationConstraint": self.region_name},
183-
)
184-
self.s3_client.get_waiter("bucket_exists").wait(Bucket=self.bucket_name)
198+
if exists:
199+
self.s3_client.delete_bucket(Bucket=self.bucket_name)
200+
201+
202+
@pytest.fixture(scope="session")
203+
def rds_testing_instance() -> RDSTestingInstance:
204+
return RDSTestingInstance("decodecloudintegrationtestsuserapi")
185205

186206

187207
@pytest.fixture(scope="session")
188-
def bucket_suffix() -> str:
189-
return datetime.datetime.now(datetime.UTC).strftime("%Y%m%d%H%M%S")
208+
def s3_testing_bucket() -> S3TestingBucket:
209+
bucket_suffix = datetime.datetime.now(datetime.UTC).strftime("%Y%m%d%H%M%S")
210+
return S3TestingBucket(bucket_suffix)
190211

191212

192213
@pytest.mark.aws

tests/integration/conftest.py

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,50 +31,62 @@
3131
from tests.conftest import REGION_NAME, RDSTestingInstance, S3TestingBucket
3232

3333

34-
@pytest.fixture(scope="module")
34+
@pytest.fixture(scope="session")
3535
def username() -> str:
3636
return "test_user"
3737

3838

39-
@pytest.fixture(scope="module")
39+
@pytest.fixture(scope="session")
4040
def user_email() -> str:
4141
return "user@example.com"
4242

4343

44-
@pytest.fixture(scope="module")
44+
@pytest.fixture(scope="session")
4545
def base_user_dir() -> str:
4646
return "test_user_dir"
4747

4848

49-
@pytest.fixture(scope="module")
49+
@pytest.fixture(scope="session")
5050
def internal_api_key_secret() -> str:
5151
return "test_internal_api_key"
5252

5353

54-
@pytest.fixture(scope="module")
54+
@pytest.fixture(scope="session")
5555
def application() -> dict[str, str]:
5656
return {"application": "app", "version": "latest", "entrypoint": "test"}
5757

5858

5959
@pytest.fixture(
60-
scope="module",
60+
scope="session",
6161
params=["local", pytest.param("aws", marks=pytest.mark.aws)],
6262
)
63-
def env(request: pytest.FixtureRequest) -> str:
64-
return cast(str, request.param)
63+
def env(
64+
request: pytest.FixtureRequest,
65+
rds_testing_instance: RDSTestingInstance,
66+
s3_testing_bucket: S3TestingBucket,
67+
) -> Generator[str, Any, None]:
68+
env = cast(str, request.param)
69+
if env == "aws":
70+
rds_testing_instance.create()
71+
s3_testing_bucket.create()
72+
yield env
73+
if env == "aws":
74+
rds_testing_instance.delete()
75+
s3_testing_bucket.delete()
6576

6677

6778
@pytest.fixture
68-
def db_session(env: str) -> Generator[Session, Any, None]:
79+
def db_session(
80+
env: str, rds_testing_instance: RDSTestingInstance
81+
) -> Generator[Session, Any, None]:
6982
if env == "local":
7083
rel_test_db_path = "./test_app.db"
7184
shutil.rmtree(rel_test_db_path, ignore_errors=True)
7285
engine = create_engine(
7386
f"sqlite:///{rel_test_db_path}", connect_args={"check_same_thread": False}
7487
)
7588
elif env == "aws":
76-
rds_instance = RDSTestingInstance("decodecloudintegrationtestsuserapi")
77-
engine = rds_instance.engine
89+
engine = rds_testing_instance.engine
7890
else:
7991
raise NotImplementedError
8092

@@ -85,15 +97,15 @@ def db_session(env: str) -> Generator[Session, Any, None]:
8597
if env == "local":
8698
os.remove(rel_test_db_path)
8799
elif env == "aws":
88-
rds_instance.cleanup()
100+
rds_testing_instance.cleanup()
89101

90102

91103
@pytest.fixture
92104
def base_filesystem(
93105
env: str,
94106
base_user_dir: str,
95107
monkeypatch_module: pytest.MonkeyPatch,
96-
bucket_suffix: str,
108+
s3_testing_bucket: S3TestingBucket,
97109
) -> Generator[FileSystem, Any, None]:
98110
if env == "local":
99111
base_user_dir = f"./{base_user_dir}"
@@ -120,17 +132,16 @@ def base_filesystem(
120132
shutil.rmtree(base_user_dir, ignore_errors=True)
121133

122134
elif env == "aws":
123-
testing_bucket = S3TestingBucket(bucket_suffix)
124135
# Update settings to use the actual unique bucket name created by S3TestingBucket
125136
monkeypatch_module.setattr(
126137
settings,
127138
"s3_bucket",
128-
testing_bucket.bucket_name,
139+
s3_testing_bucket.bucket_name,
129140
)
130141
yield S3Filesystem(
131-
base_user_dir, testing_bucket.s3_client, testing_bucket.bucket_name
142+
base_user_dir, s3_testing_bucket.s3_client, s3_testing_bucket.bucket_name
132143
)
133-
testing_bucket.cleanup()
144+
s3_testing_bucket.cleanup()
134145

135146
else:
136147
raise NotImplementedError
@@ -163,7 +174,7 @@ def override_filesystem_dep(
163174
)
164175

165176

166-
@pytest.fixture(autouse=True, scope="module")
177+
@pytest.fixture(autouse=True, scope="session")
167178
def override_auth(
168179
monkeypatch_module: pytest.MonkeyPatch, username: str, user_email: str
169180
) -> None:
@@ -174,7 +185,7 @@ def override_auth(
174185
)
175186

176187

177-
@pytest.fixture(scope="module", autouse=True)
188+
@pytest.fixture(scope="session", autouse=True)
178189
def override_internal_api_key_secret(
179190
monkeypatch_module: pytest.MonkeyPatch, internal_api_key_secret: str
180191
) -> None:
@@ -185,7 +196,7 @@ def override_internal_api_key_secret(
185196
)
186197

187198

188-
@pytest.fixture(scope="module", autouse=True)
199+
@pytest.fixture(scope="session", autouse=True)
189200
def override_email_sender(monkeypatch_module: pytest.MonkeyPatch) -> None:
190201
monkeypatch_module.setitem(
191202
app.dependency_overrides, # type: ignore
@@ -194,7 +205,7 @@ def override_email_sender(monkeypatch_module: pytest.MonkeyPatch) -> None:
194205
)
195206

196207

197-
@pytest.fixture(scope="module", autouse=True)
208+
@pytest.fixture(scope="session", autouse=True)
198209
def override_application_config(
199210
monkeypatch_module: pytest.MonkeyPatch, application: dict[str, str]
200211
) -> None:

tests/unit/core/test_filesystem.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,15 @@ def mock_aws_(self, request: pytest.FixtureRequest) -> bool:
263263

264264
@pytest.fixture(scope="class")
265265
def filesystem(
266-
self, base_dir: str, mock_aws_: bool, bucket_suffix: str
266+
self, base_dir: str, mock_aws_: bool, s3_testing_bucket: S3TestingBucket
267267
) -> Generator[S3Filesystem, Any, None]:
268268
context_manager = mock_aws if mock_aws_ else nullcontext
269269
with context_manager():
270-
testing_bucket = S3TestingBucket(bucket_suffix)
270+
s3_testing_bucket.create()
271271
yield S3Filesystem(
272-
base_dir, testing_bucket.s3_client, testing_bucket.bucket_name
272+
base_dir, s3_testing_bucket.s3_client, s3_testing_bucket.bucket_name
273273
)
274-
testing_bucket.cleanup()
274+
s3_testing_bucket.delete()
275275

276276
@pytest.fixture
277277
def data_file1(

0 commit comments

Comments
 (0)