Skip to content

Commit 52d6636

Browse files
authored
Merge pull request #180 from USACE-WaterManagement/cwbi-dev
Migrate dev to test
2 parents c4d50bc + 5e6817a commit 52d6636

40 files changed

Lines changed: 2247 additions & 105 deletions
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Python Tests on PR
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- cwbi-dev
7+
- cwbi-test
8+
- cwbi-prod
9+
paths:
10+
- "cwms_batch_events/**"
11+
- "tests/**"
12+
- "requirements.txt"
13+
- "requirements-dev.txt"
14+
- "pytest.ini"
15+
- ".github/workflows/cwbi-test-push-api.yml"
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
python-tests:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v4
27+
28+
- name: Set up Python
29+
uses: actions/setup-python@v5
30+
with:
31+
python-version: "3.13"
32+
cache: "pip"
33+
cache-dependency-path: |
34+
requirements.txt
35+
requirements-dev.txt
36+
37+
- name: Install dependencies
38+
run: python -m pip install -r requirements-dev.txt
39+
40+
- name: Run pytest
41+
run: pytest --cov-report=html --cov-report=json
42+
43+
- name: Upload coverage HTML report
44+
if: always()
45+
uses: actions/upload-artifact@v4
46+
with:
47+
name: python-htmlcov
48+
path: htmlcov/
49+
50+
- name: Add coverage summary
51+
if: always()
52+
run: |
53+
python - <<'PY'
54+
import json
55+
import os
56+
from pathlib import Path
57+
58+
coverage_file = Path("coverage.json")
59+
if not coverage_file.exists():
60+
raise SystemExit("coverage.json was not generated")
61+
62+
data = json.loads(coverage_file.read_text())
63+
total = data["totals"]["percent_covered_display"]
64+
65+
summary = Path(os.environ["GITHUB_STEP_SUMMARY"])
66+
summary.write_text(
67+
"## Python Coverage\n\n"
68+
f"- Total coverage: `{total}%`\n"
69+
"- HTML report: download the `python-htmlcov` artifact from this workflow run.\n",
70+
encoding="utf-8",
71+
)
72+
PY

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ htmlcov/
4242
.nox/
4343
.coverage
4444
.coverage.*
45+
coverage.json
4546
.cache
4647
nosetests.xml
4748
coverage.xml

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@
2525
## Notes
2626
* Type Standardization
2727
* TypeScript types are generated from the API for use in the frontend using [OpenAPI TypeScript](https://openapi-ts.dev/).
28-
* If API types are updated or modified, run `npm run generate:types` to update the type definitions.
28+
* If API types are updated or modified, run `npm run generate:types` to update the type definitions.
29+
* Python Testing
30+
* Run `pytest` from the project root to execute the Python unit test suite.
31+
* The suite is configured to report coverage for `cwms_batch_events` and should stay fast and free of external dependencies.

cwms_batch_events/api/routers/scripts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def post_script(
6363
return job_db.store_script(payload)
6464
except ValueError as e:
6565
raise HTTPException(
66-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
66+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(e)
6767
)
6868
except SlugError as e:
6969
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
@@ -87,7 +87,7 @@ def put_script(
8787
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e))
8888
except ValueError as e:
8989
raise HTTPException(
90-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
90+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(e)
9191
)
9292

9393

cwms_batch_events/core/auth/user/dependencies.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fastapi import Depends, HTTPException, Request, status
22
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
33

4+
from cachetools import TTLCache
45
from cwms_batch_events.core.auth.user.jwt import verify_jwt
56
from cwms_batch_events.core.auth.user.models import User
67
from cwms_batch_events.core.auth.user.roles import (
@@ -24,6 +25,8 @@
2425
description="Use format: apikey <your-api-key>",
2526
)
2627

28+
user_cache: TTLCache[str, User] = TTLCache(maxsize=1024, ttl=300)
29+
2730

2831
async def get_auth_header_for_docs(
2932
bearer: str = Depends(bearer_scheme),
@@ -47,6 +50,10 @@ async def get_auth_credentials(
4750
async def get_current_user_cwms(
4851
credentials: HTTPAuthorizationCredentials = Depends(get_auth_credentials),
4952
) -> User:
53+
cache_key = f"{credentials.scheme}:{credentials.credentials}"
54+
if cache_key in user_cache:
55+
return user_cache[cache_key]
56+
5057
if credentials.scheme.lower() == "bearer":
5158
token = credentials.credentials
5259
try:
@@ -71,13 +78,16 @@ async def get_current_user_cwms(
7178

7279
allowed_offices = get_user_allowed_offices(cda_user)
7380
admin_offices = get_user_admin_offices(cda_user)
74-
return User(
81+
user = User(
7582
username=cda_user.user_name,
7683
offices=allowed_offices,
7784
admin_offices=admin_offices,
7885
roles=cda_user.roles,
7986
)
8087

88+
user_cache[cache_key] = user
89+
return user
90+
8191

8292
async def get_current_user_mock() -> User:
8393
return User(

cwms_batch_events/core/auth/user/jwt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
ISSUER = {
1818
"PROD": "https://identity.sec.usace.army.mil/auth/realms/cwbi",
19-
"TEST": "https://identity-test.cwbi.us/auth/realms/cwbi",
19+
"TEST": "https://identity-test.cwbi.mil/auth/realms/cwbi",
2020
}
2121

2222

cwms_batch_events/lambdas/dispatch_job/dispatcher.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def lambda_handler(event, context):
100100
external_job_id = dispatch_job(message)
101101
except ClientError:
102102
logger.exception("Failed to submit batch job for message: %s", message)
103+
raise
103104

104105
try:
105106
bind_request = BindExternalJobIdRequest(external_job_id=external_job_id)

pytest.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[pytest]
2+
testpaths = tests
3+
addopts = -ra --strict-markers --cov=cwms_batch_events --cov-branch --cov-report=term-missing
4+
markers =
5+
unit: fast isolated tests
6+
integration: tests that exercise component boundaries

requirements-dev.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
-r requirements.txt
22
pytest
3-
docker
3+
pytest-cov
4+
pytest-trio
5+
docker

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
boto3
2+
cachetools
23
fastapi
34
psycopg2-binary
45
pydantic

0 commit comments

Comments
 (0)