Compare commits

..

10 Commits

Author SHA1 Message Date
Barcode Betty 135064fc10 Merge pull request 'fix(ci): resolve dev lint + typecheck failures (CAR-1330)' (#48) from betty/car-1330-dev-ci-fix into dev
CI / lint (push) Successful in 7s
CI / typecheck (push) Successful in 18s
CI / test (push) Successful in 22s
CI / build-and-push (push) Successful in 1m6s
2026-06-11 04:17:51 +00:00
Savannah Savings b141377b02 Merge pull request 'fix(api): document dispose_engine lazy import + regression test (CAR-1135)' (#45) from barcode-betty/fix-car-1135-dispose-engine into dev
CI / lint (push) Successful in 4s
CI / typecheck (push) Successful in 19s
CI / test (push) Successful in 23s
CI / build-and-push (push) Successful in 1m30s
2026-06-10 05:13:18 +00:00
Barcode Betty a3a01eefe2 Merge pull request 'fix(ci): simplify Push step to match auth pattern (CAR-1362)' (#53) from betty/car-1362-push-unknown-fix into dev
CI / lint (push) Successful in 4s
CI / typecheck (push) Successful in 18s
CI / test (push) Successful in 24s
CI / build-and-push (push) Successful in 1m45s
2026-06-10 04:10:58 +00:00
Barcode Betty 354e26295c fix(ci): simplify Push step to match auth pattern (CAR-1362)
CI / lint (pull_request) Successful in 5s
CI / typecheck (pull_request) Successful in 20s
CI / test (pull_request) Successful in 22s
CI / build-and-push (pull_request) Has been skipped
The Push Docker image step is failing post-merge of CAR-1362 with
buildx "unknown" error after layers push successfully. The pre-existing
failure was masked by the cache export error.

Simplify the Push step to match the proven-green cartsnitch/auth/ci.yml
pattern: drop `file: ./Dockerfile` (default) and `build-args:`
(APT_CACHE_BUST is only used to bust apt cache in stage 1 of multi-
stage build, not needed for the rebuilt image). Keep `if: github.event_name
== "push"` to skip on pull_request events.

Diff: 4 lines removed from .gitea/workflows/ci.yml Push step.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-10 04:08:53 +00:00
Barcode Betty 30a447674d Merge pull request 'fix(ci): remove GHA cache-from/cache-to (CAR-1357)' (#52) from betty/car-1362-remove-gha-cache-dev into dev
CI / lint (push) Successful in 6s
CI / typecheck (push) Successful in 18s
CI / test (push) Successful in 21s
CI / build-and-push (push) Failing after 1m8s
2026-06-09 18:03:04 +00:00
Barcode Betty 7a7d8f451e fix(ci): remove GHA cache-from/cache-to (CAR-1357)
CI / lint (pull_request) Successful in 9s
CI / typecheck (pull_request) Successful in 18s
CI / test (pull_request) Successful in 23s
CI / build-and-push (pull_request) Has been skipped
The build-and-push job fails post-merge of CAR-1356 REGISTRY_TOKEN fix:
cache-from/cache-to: type=gha backend does not exist on Gitea. Build
succeeds but post-build cache export fails and cascades to skipping the
Push Docker image step. Confirmed in uat run 3444 + dev run 3445.

Per CAR-1362, drop cache-from and cache-to from both Build and Push
Docker image steps. Matches proven-green cartsnitch/auth/ci.yml pattern.

Refs: CAR-1362, CAR-1356, CAR-1330, CAR-1357.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-09 18:01:21 +00:00
Barcode Betty 79e8baa609 fix(ci): use REGISTRY_TOKEN for build-and-push registry login (CAR-1330)
CI / lint (push) Successful in 4s
CI / typecheck (push) Successful in 17s
CI / test (push) Successful in 25s
CI / build-and-push (push) Failing after 54s
Squashed fix swaps github.token → secrets.REGISTRY_TOKEN at .gitea/workflows/ci.yml:121, matching the proven-green cartsnitch/auth pattern (CAR-1009). Parity fix with uat PR #49 to prevent reintroduction on next dev→uat promotion.

Note: includes 3 absorbed lint/typecheck commits from PR #48 (already merged to dev via #48) to unblock CI on this branch. No app code changes; one-line CI config swap only.

QA: PR #50 approved by @cs_charlie (review id 4616); CI run 3443 lint/typecheck/test all green.
Co-authored-by: Barcode Betty <32+cs_betty@noreply.git.farh.net>
Co-committed-by: Barcode Betty <32+cs_betty@noreply.git.farh.net>
2026-06-09 17:47:11 +00:00
Barcode Betty 7b595744e1 fix(api): mypy no-redef and no-any-return errors on dev (CAR-1335)
CI / lint (pull_request) Successful in 5s
CI / typecheck (pull_request) Successful in 18s
CI / test (pull_request) Successful in 22s
CI / build-and-push (pull_request) Has been skipped
The api typecheck job is continue-on-error but still posts a failure
status that blocks merges. Three pre-existing mypy errors on dev were
inherited by every PR based on it:

1. middleware/rate_limit.py: duplicate 'name already defined' for
   _public_limiter, _auth_limiter, _auth_strict_limiter (declared at
   lines 111-113 and again at 124-126). The second set is redundant
   because actual assignment happens inside the if/else below.
2. cache.py:43 - 'Returning Any' from .get(); the redis client's get()
   return type isn't narrowed to bytes|str, so the final 'return value'
   branch is Any. Wrap with str() to satisfy the declared str|None.
3. middleware/rate_limit.py:150 - 'Returning Any' from _get_client_ip.
   request.headers.get() and request.client.host are typed Any; wrap
   the branches with str() to match the declared str return.

Refs CAR-1335.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-09 05:25:41 +00:00
Barcode Betty 4877513bbf style: ruff format conformance (CAR-1335)
- tests/test_openapi.py: collapse 2 blank lines to 1 (ruff format)
- tests/conftest.py: collapse 2 blank lines to 1 (ruff format)

These format nits block lint (a hard gate). The conftest.py one was
introduced in CAR-1132 (#42) and would have blocked every subsequent PR
on dev until fixed.

Refs CAR-1335, CAR-1135.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-09 05:23:36 +00:00
Barcode Betty 9e46bdc460 fix(api): document dispose_engine lazy import + regression test (CAR-1135)
CI / lint (pull_request) Failing after 4s
CI / typecheck (pull_request) Failing after 18s
CI / test (pull_request) Successful in 22s
CI / build-and-push (pull_request) Has been skipped
- main.py: add docstring inside the lifespan function explaining why
  dispose_engine is lazy-imported rather than top-level. The original
  import path (top-level) crashed the container at import time with
  'ImportError: cannot import name dispose_engine from cartsnitch_api.database'
  when database.py was stale or stripped during a CI build. Lazy import
  keeps the engine disposal behavior while preventing the module-load
  crash.
- tests/test_openapi.py: add test_dispose_engine_importable_from_database
  that asserts dispose_engine is importable and callable. This is the
  exact path the deployed UAT image was failing on, captured as a
  regression test so a future regression lands in CI before deploy.

Refs CAR-1135.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-09 05:13:08 +00:00
5 changed files with 24 additions and 10 deletions
+1 -7
View File
@@ -118,7 +118,7 @@ jobs:
echo "CalVer tag: $VERSION" echo "CalVer tag: $VERSION"
- name: Log in to Gitea Container Registry - name: Log in to Gitea Container Registry
run: echo "${{ github.token }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin
- name: Extract metadata - name: Extract metadata
id: meta id: meta
@@ -140,8 +140,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
APT_CACHE_BUST=${{ github.run_id }} APT_CACHE_BUST=${{ github.run_id }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan api image for vulnerabilities - name: Scan api image for vulnerabilities
uses: anchore/scan-action@v5 uses: anchore/scan-action@v5
@@ -162,13 +160,9 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./Dockerfile
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: |
APT_CACHE_BUST=${{ github.run_id }}
cache-from: type=gha
- name: Create git tag - name: Create git tag
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+1 -1
View File
@@ -40,7 +40,7 @@ class CacheClient:
return None return None
if isinstance(value, bytes): if isinstance(value, bytes):
return value.decode("utf-8", errors="replace") return value.decode("utf-8", errors="replace")
return value return str(value)
async def set(self, key: str, value: str, ttl_seconds: int = 300) -> None: async def set(self, key: str, value: str, ttl_seconds: int = 300) -> None:
if not self._client: if not self._client:
+6
View File
@@ -25,6 +25,12 @@ from cartsnitch_api.routes.user import router as user_router
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# Lazy import: keep `dispose_engine` out of the top-level imports so a
# stale or partially-built database.py never breaks module load on
# container start. The function is required for graceful pool cleanup
# on shutdown; if the import fails, the cache_client.close() that
# follows the yield would mask it. See CAR-1135 for the original
# ImportError that motivated this pattern.
from cartsnitch_api.database import dispose_engine from cartsnitch_api.database import dispose_engine
await cache_client.initialize() await cache_client.initialize()
+2 -2
View File
@@ -147,8 +147,8 @@ def _get_client_ip(request: Request) -> str:
"""Extract client IP, respecting X-Forwarded-For behind a reverse proxy.""" """Extract client IP, respecting X-Forwarded-For behind a reverse proxy."""
forwarded = request.headers.get("x-forwarded-for") forwarded = request.headers.get("x-forwarded-for")
if forwarded: if forwarded:
return forwarded.split(",")[0].strip() return str(forwarded.split(",")[0].strip())
return request.client.host if request.client else "unknown" return str(request.client.host) if request.client else "unknown"
def _get_rate_limit_key(request: Request) -> tuple[str, RateLimitBackend]: def _get_rate_limit_key(request: Request) -> tuple[str, RateLimitBackend]:
+14
View File
@@ -3,8 +3,22 @@
import pytest import pytest
from httpx import ASGITransport, AsyncClient from httpx import ASGITransport, AsyncClient
from cartsnitch_api.database import dispose_engine
from cartsnitch_api.main import app from cartsnitch_api.main import app
def test_dispose_engine_importable_from_database():
"""Regression for CAR-1135: api main.py used to import dispose_engine
at module level. A stale database.py (no dispose_engine) crashed the
container at import time with ImportError on line 9. The fix moved
the import inside the lifespan function, but `dispose_engine` must
still be importable from `cartsnitch_api.database` for the lifespan
teardown to actually close pooled connections.
"""
assert callable(dispose_engine)
assert dispose_engine.__name__ == "dispose_engine"
EXPECTED_ROUTES = [ EXPECTED_ROUTES = [
# Auth (3 — register/login/refresh are handled by Better-Auth service) # Auth (3 — register/login/refresh are handled by Better-Auth service)
("get", "/auth/me"), ("get", "/auth/me"),