Skip to content

Commit d5ff8ff

Browse files
Merge pull request #13 from nasa/GESDISCUMU-5176-Fix-broken-unit-tests-for-gap-detection
All unit tests passing with coverage
2 parents 5456f5c + 01e9d2c commit d5ff8ff

File tree

12 files changed

+2371
-393
lines changed

12 files changed

+2371
-393
lines changed

.github/workflows/test-and-release.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ on:
99
- GESDISCUMU-5118-migrate-gap-dection-codebase-and-template
1010
jobs:
1111
build-unit-tests:
12-
if: false # Temporary disabling the build-unit-tests jobs. We have plans to fix the unit tests before renabling this job.
1312
runs-on: ubuntu-latest
1413
steps:
1514
- name: Checkout code

src/shared/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def get_connection_pool() -> ConnectionPool:
7878
max_size=10,
7979
max_lifetime=7200, # 2 hours
8080
max_idle=900, # 15 minute idle timeout
81+
open=True
8182
)
8283

8384
logger.debug("Connection pool initialized")

test/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ RUN pip install -U -r test_requirements.txt
77
COPY test/ /app/test/
88
COPY src/ /app/src/
99
COPY src/shared/gap_schema.sql /app/
10-
COPY src/knownGap/schema.json /app/
1110

1211
ENTRYPOINT []

test/conftest.py

Lines changed: 134 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def setup_database():
4141
init_sql = f.read()
4242
with conn.cursor() as cur:
4343
cur.execute("DROP TABLE IF EXISTS gaps CASCADE;")
44+
cur.execute("DROP TABLE IF EXISTS reasons CASCADE;")
4445
cur.execute("DROP TABLE IF EXISTS collections CASCADE;")
4546
cur.execute(init_sql)
4647

@@ -59,16 +60,24 @@ def setup_test_data():
5960

6061
with get_db_connection() as conn:
6162
with conn.cursor() as cur:
62-
cur.execute("TRUNCATE TABLE gaps")
63+
# Clear existing data
64+
cur.execute("TRUNCATE TABLE gaps CASCADE")
65+
cur.execute("TRUNCATE TABLE reasons CASCADE")
6366
cur.execute("TRUNCATE TABLE collections CASCADE")
67+
68+
# Disable trigger to prevent automatic gap creation
6469
cur.execute("ALTER TABLE collections DISABLE TRIGGER collection_insert_trigger")
70+
71+
# Insert test collections
6572
cur.execute("""
6673
INSERT INTO collections (collection_id, temporal_extent_start, temporal_extent_end)
6774
VALUES (%s, %s, %s), (%s, %s, %s)
6875
""", (
6976
TEST_COLLECTION_ID, DEFAULT_DATE, DEFAULT_END_DATE,
7077
SECOND_COLLECTION_ID, DEFAULT_DATE, DEFAULT_END_DATE
7178
))
79+
80+
# Re-enable trigger
7281
cur.execute("ALTER TABLE collections ENABLE TRIGGER collection_insert_trigger")
7382
conn.commit()
7483

@@ -86,28 +95,48 @@ def mock_sql():
8695

8796
# Helper Functions
8897
def create_test_partition(collection_id, conn):
89-
"""Create a test partition for a collection."""
98+
"""Create test partitions for both gaps and reasons tables."""
9099
with conn.cursor() as cur:
91100
safe_collection_id = re.sub(r'\W+', '_', collection_id)
92-
partition_name = f"gaps_{safe_collection_id}"
101+
102+
# Create gaps partition
103+
gaps_partition_name = f"gaps_{safe_collection_id}"
93104
cur.execute("""
94105
SELECT 1 FROM pg_class c
95106
JOIN pg_namespace n ON n.oid = c.relnamespace
96107
WHERE c.relname = %s AND n.nspname = 'public'
97-
""", (partition_name,))
108+
""", (gaps_partition_name,))
109+
98110
if cur.fetchone() is None:
99111
cur.execute(
100112
SQL("CREATE TABLE {} PARTITION OF gaps FOR VALUES IN ({})").format(
101-
Identifier(partition_name), Literal(collection_id)
113+
Identifier(gaps_partition_name), Literal(collection_id)
102114
)
103115
)
104-
constraint_name = f"{partition_name}_no_overlap"
116+
# Add exclusion constraint to prevent overlapping time ranges
117+
constraint_name = f"{gaps_partition_name}_no_overlap"
105118
cur.execute(
106119
SQL("ALTER TABLE {} ADD CONSTRAINT {} EXCLUDE USING gist (tsrange(start_ts, end_ts) WITH &&)").format(
107-
Identifier(partition_name), Identifier(constraint_name)
120+
Identifier(gaps_partition_name), Identifier(constraint_name)
121+
)
122+
)
123+
124+
# Create reasons partition
125+
reasons_partition_name = f"reasons_{safe_collection_id}"
126+
cur.execute("""
127+
SELECT 1 FROM pg_class c
128+
JOIN pg_namespace n ON n.oid = c.relnamespace
129+
WHERE c.relname = %s AND n.nspname = 'public'
130+
""", (reasons_partition_name,))
131+
132+
if cur.fetchone() is None:
133+
cur.execute(
134+
SQL("CREATE TABLE {} PARTITION OF reasons FOR VALUES IN ({})").format(
135+
Identifier(reasons_partition_name), Literal(collection_id)
108136
)
109137
)
110-
return partition_name
138+
139+
return gaps_partition_name, reasons_partition_name
111140

112141
def create_granule(start, end, collection_id=TEST_COLLECTION_ID):
113142
return {
@@ -144,14 +173,33 @@ def create_sqs_event(records_data):
144173
return {"Records": sqs_records}
145174

146175
def insert_gap(collection_id, start_ts, end_ts):
176+
"""Insert a gap without a reason."""
177+
from utils import get_db_connection
178+
179+
with get_db_connection() as conn:
180+
with conn.cursor() as cur:
181+
cur.execute("""
182+
INSERT INTO gaps (collection_id, start_ts, end_ts)
183+
VALUES (%s, %s, %s)
184+
""", (collection_id, start_ts, end_ts))
185+
186+
def insert_gap_with_reason(collection_id, start_ts, end_ts, reason):
187+
"""Insert a gap and its associated reason."""
147188
from utils import get_db_connection
148189

149190
with get_db_connection() as conn:
150191
with conn.cursor() as cur:
192+
# Insert the gap
151193
cur.execute("""
152194
INSERT INTO gaps (collection_id, start_ts, end_ts)
153195
VALUES (%s, %s, %s)
154196
""", (collection_id, start_ts, end_ts))
197+
198+
# Insert the reason
199+
cur.execute("""
200+
INSERT INTO reasons (collection_id, start_ts, end_ts, reason)
201+
VALUES (%s, %s, %s, %s)
202+
""", (collection_id, start_ts, end_ts, reason))
155203

156204
def get_gaps(collection_id):
157205
from utils import get_db_connection
@@ -164,6 +212,25 @@ def get_gaps(collection_id):
164212
)
165213
return cur.fetchall()
166214

215+
def get_gaps_with_reasons(collection_id):
216+
"""Get gaps with their associated reasons."""
217+
from utils import get_db_connection
218+
219+
with get_db_connection() as conn:
220+
with conn.cursor() as cur:
221+
cur.execute("""
222+
SELECT g.start_ts, g.end_ts, r.reason
223+
FROM gaps g
224+
LEFT JOIN reasons r ON (
225+
g.collection_id = r.collection_id
226+
AND g.start_ts = r.start_ts
227+
AND g.end_ts = r.end_ts
228+
)
229+
WHERE g.collection_id = %s
230+
ORDER BY g.start_ts
231+
""", (collection_id,))
232+
return cur.fetchall()
233+
167234
def get_gap_count(collection_id):
168235
from utils import get_db_connection
169236

@@ -195,62 +262,85 @@ def create_api_test_event(http_method, path, body=None, query_string_parameters=
195262
return event
196263

197264
def get_record(conn, collection_id, start_ts, end_ts):
265+
"""Get a gap record with its reason if available."""
198266
with conn.cursor() as cur:
199267
cur.execute("""
200-
SELECT gap_id, collection_id, start_ts, end_ts, reason
201-
FROM gaps
202-
WHERE collection_id = %s
203-
AND start_ts = %s
204-
AND end_ts = %s
268+
SELECT g.gap_id, g.collection_id, g.start_ts, g.end_ts, r.reason
269+
FROM gaps g
270+
LEFT JOIN reasons r ON (
271+
g.collection_id = r.collection_id
272+
AND g.start_ts = r.start_ts
273+
AND g.end_ts = r.end_ts
274+
)
275+
WHERE g.collection_id = %s
276+
AND g.start_ts = %s
277+
AND g.end_ts = %s
205278
""", (collection_id, start_ts, end_ts))
206279
return cur.fetchone()
207280

208281
def seed_test_data(test_data, collection_id=TEST_COLLECTION_ID):
209282
"""Seed test data for knownGap tests."""
210-
211283
from utils import get_db_connection
284+
212285
with get_db_connection() as conn:
213286
with conn.cursor() as cur:
214-
cur.execute("SELECT 1 FROM collections WHERE collection_id = %s", (TEST_COLLECTION_ID,))
287+
# Ensure collection exists
288+
cur.execute("SELECT 1 FROM collections WHERE collection_id = %s", (collection_id,))
215289
if not cur.fetchone():
216290
cur.execute(
217291
"INSERT INTO collections (collection_id, temporal_extent_start, temporal_extent_end) VALUES (%s, %s, %s)",
218-
(TEST_COLLECTION_ID, DEFAULT_DATE, DEFAULT_END_DATE)
292+
(collection_id, DEFAULT_DATE, DEFAULT_END_DATE)
219293
)
220294

221-
safe_id = re.sub(r'\W+', '_', TEST_COLLECTION_ID)
222-
partition_name = f"gaps_{safe_id}"
223-
224-
# Check if partition exists
225-
cur.execute(f"""
226-
SELECT 1 FROM pg_class c
227-
JOIN pg_namespace n ON n.oid = c.relnamespace
228-
WHERE c.relname = '{partition_name}' AND n.nspname = 'public'
229-
""")
230-
231-
if not cur.fetchone():
232-
# Create partition
233-
cur.execute(f"""
234-
CREATE TABLE {partition_name} PARTITION OF gaps
235-
FOR VALUES IN ('{TEST_COLLECTION_ID}')
236-
""")
237-
cur.execute(f"""
238-
ALTER TABLE {partition_name} ADD CONSTRAINT {partition_name}_no_overlap
239-
EXCLUDE USING gist (tsrange(start_ts, end_ts) WITH &&)
240-
""")
295+
# Ensure partitions exist
296+
create_test_partition(collection_id, conn)
241297

242-
cur.execute("DELETE FROM gaps WHERE collection_id = %s", (TEST_COLLECTION_ID,))
298+
# Clear existing test data
299+
cur.execute("DELETE FROM gaps WHERE collection_id = %s", (collection_id,))
300+
cur.execute("DELETE FROM reasons WHERE collection_id = %s", (collection_id,))
243301

244302
# Insert test data
245303
for data in test_data:
304+
start_ts = data.get('start_ts')
305+
end_ts = data.get('end_ts')
306+
reason = data.get('reason')
307+
308+
# Insert gap
246309
cur.execute(
247-
"INSERT INTO gaps (collection_id, start_ts, end_ts, reason) VALUES (%s, %s, %s, %s)",
248-
(
249-
TEST_COLLECTION_ID,
250-
data.get('start_ts'),
251-
data.get('end_ts'),
252-
data.get('reason')
253-
)
310+
"INSERT INTO gaps (collection_id, start_ts, end_ts) VALUES (%s, %s, %s)",
311+
(collection_id, start_ts, end_ts)
254312
)
313+
314+
# Insert reason if provided
315+
if reason:
316+
cur.execute(
317+
"INSERT INTO reasons (collection_id, start_ts, end_ts, reason) VALUES (%s, %s, %s, %s)",
318+
(collection_id, start_ts, end_ts, reason)
319+
)
255320

256321
conn.commit()
322+
323+
def get_reason(collection_id, start_ts, end_ts):
324+
"""Get the reason for a specific gap."""
325+
from utils import get_db_connection
326+
327+
with get_db_connection() as conn:
328+
with conn.cursor() as cur:
329+
cur.execute("""
330+
SELECT reason FROM reasons
331+
WHERE collection_id = %s AND start_ts = %s AND end_ts = %s
332+
""", (collection_id, start_ts, end_ts))
333+
result = cur.fetchone()
334+
return result[0] if result else None
335+
336+
def insert_reason(collection_id, start_ts, end_ts, reason):
337+
"""Insert a reason for an existing gap."""
338+
from utils import get_db_connection
339+
340+
with get_db_connection() as conn:
341+
with conn.cursor() as cur:
342+
cur.execute("""
343+
INSERT INTO reasons (collection_id, start_ts, end_ts, reason)
344+
VALUES (%s, %s, %s, %s)
345+
""", (collection_id, start_ts, end_ts, reason))
346+
conn.commit()

test/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ pytest-mock
1111
jsonschema==4.23.0
1212
aiohttp
1313
aioboto3
14+
pytest-asyncio
15+
aioresponses

0 commit comments

Comments
 (0)