Files
jonatanritter 923848af33 feat: service scaffold, k8s test infra, health
FastAPI + SQLAlchemy async + Alembic scaffold, src-layout (mirrors the
sowai-fiscal lib's own convention), pyproject wired to sowai-fiscal@v0.1.0
via git+https (uv.lock pins the commit). Makefile mirrors auto/Makefile's
k8s-test workflow: syncs into /app/fiscal-svc in the SAME auto-tests pod,
against a dedicated fiscal_svc_test database on the shared Postgres
sidecar, serialized by the SAME lock file the auto uses on purpose so the
two repos' test runs never race in the pod. Dockerfile installs git (the
git+https dependency needs it at uv sync time) and splits the dependency
layer from the project's own editable install for build caching.

GET /v1/health -> {"status": "ok"}, verified green via `make k8s-test`.
2026-07-22 16:12:11 -03:00

97 lines
5.5 KiB
Makefile

# SowAI Fiscal Service — dev workflow (tests run IN-CLUSTER on the sow-dev
# k8s cluster, in the SAME `auto-tests` pod/Postgres sidecar the `auto`
# backend's own suite uses — see `auto/Makefile`'s header comment for that
# pod's shape). This repo syncs into its OWN tree (`/app/fiscal-svc`, never
# touching `/app/backend`) and runs against its OWN database
# (`fiscal_svc_test`, created on first use below, since the sidecar's
# `POSTGRES_DB` only pre-creates `auto_test`) — but is serialized by the
# SAME lock file (`/tmp/auto-k8s-test.lock`) as the auto's own `make
# k8s-test`, on purpose (plan 2026-07-17-fiscal-svc-f2-servico.md, Task 1):
# one pod, one Postgres sidecar, one shared lock across both repos' test
# runs, for free — a `make k8s-test` here and one in `auto/` from the same
# machine can never race each other in the pod.
NS := autopecas-dev
POD = $(shell kubectl -n $(NS) get pod -l app=auto-tests -o jsonpath='{.items[0].metadata.name}')
FISCAL_SVC_TEST_DATABASE_URL := postgresql+asyncpg://postgres:postgres@localhost:5432/fiscal_svc_test
.PHONY: k8s-sync k8s-deps k8s-test k8s-test-file k8s-shell k8s-status
# Stream this repo into /app/fiscal-svc in the runner (excludes .venv/.git/
# caches). Also ensures `git` (this service's pyproject depends on
# `sowai-fiscal` via git+https, EMENDA F1 -- `uv sync` needs the binary at
# resolve time) and `psql`/`createdb` (the migration tests shell out to
# them) are present in the runner container, and that the `fiscal_svc_test`
# database itself exists on the Postgres sidecar. All three are lost
# whenever the runner container restarts (none ship in the base image /
# get created for us), so all three are (idempotently, fast when already
# present/existing) redone on every sync -- same rationale as the auto's own
# k8s-sync ensuring `psql`/WeasyPrint libs.
#
# The tar OVERLAYS (never removes) -- same reasoning as the auto's own
# k8s-sync: the pod is SHARED across worktrees/branches (of EITHER repo), so
# `src/`/`tests/`/`alembic/` are pruned before extracting, to avoid a ghost
# file from a previous sync poisoning this run.
k8s-sync:
@echo "→ ensure git in $(POD)"
@kubectl -n $(NS) exec $(POD) -c runner -- bash -c 'command -v git >/dev/null 2>&1 || (apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq git >/dev/null 2>&1)' 2>/dev/null
@echo "→ ensure psql/createdb in $(POD)"
@kubectl -n $(NS) exec $(POD) -c runner -- bash -c 'command -v psql >/dev/null 2>&1 || (apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq postgresql-client >/dev/null 2>&1)' 2>/dev/null
@echo "→ ensure database fiscal_svc_test in $(POD)"
@kubectl -n $(NS) exec $(POD) -c runner -- bash -c 'createdb -h localhost -U postgres fiscal_svc_test 2>/dev/null || true'
@echo "→ prune stale src/tests/alembic in $(POD) (o tar sobrepõe, não remove)"
@kubectl -n $(NS) exec $(POD) -c runner -- bash -c 'mkdir -p /app/fiscal-svc && rm -rf /app/fiscal-svc/src /app/fiscal-svc/tests /app/fiscal-svc/alembic' 2>/dev/null
@echo "→ sync repo into $(POD)"
@tar czf - --exclude='.venv' --exclude='.git' --exclude='__pycache__' --exclude='*.pyc' --exclude='.pytest_cache' -C . . 2>/dev/null \
| kubectl -n $(NS) exec -i $(POD) -c runner -- tar xzf - -C /app/fiscal-svc 2>/dev/null
@echo "✅ synced"
# Re-resolve deps in the pod (run after changing pyproject/uv.lock).
k8s-deps:
@kubectl -n $(NS) exec $(POD) -c runner -- bash -c 'cd /app/fiscal-svc && uv sync' 2>&1 | grep -v xattr
# ---------------------------------------------------------------------------
# SERIALIZAÇÃO DO POD (lock local): MESMO arquivo de lock do `auto/Makefile`,
# de propósito (ver o comentário do topo). O `pkill -f pytest` abaixo é
# seguro precisamente PORQUE o lock é compartilhado -- por construção, nenhum
# pytest legítimo (deste repo OU do auto) pode estar rodando quando esta
# seção crítica começa, já que ambos os `make k8s-test` respeitam o mesmo
# arquivo antes de disparar pytest.
# ---------------------------------------------------------------------------
K8S_TEST_LOCK := /tmp/auto-k8s-test.lock
define K8S_LOCKED_RUN
@bash -c 'while ! mkdir "$(K8S_TEST_LOCK)" 2>/dev/null; do \
p=$$(cat "$(K8S_TEST_LOCK)/pid" 2>/dev/null); \
if [ -n "$$p" ] && ! kill -0 "$$p" 2>/dev/null; then \
echo "🔓 lock órfão (pid $$p morto) — assumindo"; rm -rf "$(K8S_TEST_LOCK)"; continue; \
fi; \
echo "⏳ pod de testes em uso (pid $${p:-?}) — aguardando 20s..."; sleep 20; \
done; \
echo $$$$ > "$(K8S_TEST_LOCK)/pid"; \
trap "rm -rf $(K8S_TEST_LOCK)" EXIT INT TERM; \
$(MAKE) k8s-sync && \
kubectl -n $(NS) exec $(POD) -c runner -- bash -c "pkill -f [p]ytest 2>/dev/null; true" && \
kubectl -n $(NS) exec $(POD) -c runner -- bash -c "cd /app/fiscal-svc && TEST_DATABASE_URL=$(FISCAL_SVC_TEST_DATABASE_URL) uv run pytest -q $(1)" 2>&1 | grep -v xattr'
endef
# Full suite in-cluster (serializada pelo lock). NOTE: `TEST_DATABASE_URL`
# is passed explicitly on the pytest invocation above, NOT left to the
# container's own env -- the runner container's env already carries a
# `TEST_DATABASE_URL` pointing at the auto's `auto_test` database (baked
# into the shared pod spec, `k8s/test-runner.yaml` in the auto repo); without
# this override this service's suite would silently run against the WRONG
# database (or a database it has no schema in).
k8s-test:
$(call K8S_LOCKED_RUN,)
# Single file/selector (serializada pelo lock): make k8s-test-file file=tests/test_health.py
k8s-test-file:
$(call K8S_LOCKED_RUN,$(file))
k8s-shell:
@kubectl -n $(NS) exec -it $(POD) -c runner -- bash
k8s-status:
@kubectl -n $(NS) get pods,svc