Skip to content

Commit 232759f

Browse files
authored
chore: speed-up of CI tests (#46)
* wait instead of curl polling, + coverage_core=sysmon * waiting for user_seed to complete before starting * k8s integration -> e2e, also update pytest marks * default test loop scope = session * coverage core + renames of fixtures * moved k8s tests to e2e, fixed inconsistencies in user settings * Introduce domain exception hierarchy for transport-agnostic services - Add base exceptions in app/domain/exceptions.py (NotFoundError, ValidationError, ThrottledError, ConflictError, UnauthorizedError, ForbiddenError, InvalidStateError, InfrastructureError) - Create domain-specific exceptions for execution, saga, notification, saved_script, replay, and user/auth modules - Update all services to throw domain exceptions instead of HTTPException - Add single exception handler middleware that maps to HTTP status codes - Add documentation in docs/architecture/domain-exceptions.md Services no longer know about HTTP semantics. The middleware handles all mapping from domain exceptions to JSON responses. * structural logging+doc added * search_text field unification in events * removed filter_fields hardcoded stuff from replay endpoints * removed extra conversion function from replay api * database context simplification (use beanie instead of direct pymongo calls) * moved from stdlib dataclass to pydantic dataclass
1 parent a44bd24 commit 232759f

File tree

313 files changed

+8572
-11659
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

313 files changed

+8572
-11659
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: 'Docker Image Cache'
2+
description: 'Cache and load Docker images for CI jobs'
3+
4+
inputs:
5+
images:
6+
description: 'Space-separated list of Docker images to cache'
7+
required: true
8+
9+
runs:
10+
using: 'composite'
11+
steps:
12+
- name: Generate cache key from images
13+
id: cache-key
14+
shell: bash
15+
env:
16+
IMAGES_INPUT: ${{ inputs.images }}
17+
run: |
18+
# Create a stable hash from the sorted image list
19+
# Using env var to prevent script injection
20+
IMAGES_HASH=$(echo "$IMAGES_INPUT" | tr ' ' '\n' | sort | md5sum | cut -d' ' -f1)
21+
echo "key=docker-${{ runner.os }}-${IMAGES_HASH}" >> $GITHUB_OUTPUT
22+
23+
- name: Cache Docker images
24+
uses: actions/cache@v5
25+
id: docker-cache
26+
with:
27+
path: /tmp/docker-cache
28+
key: ${{ steps.cache-key.outputs.key }}
29+
30+
- name: Load cached Docker images
31+
if: steps.docker-cache.outputs.cache-hit == 'true'
32+
shell: bash
33+
run: |
34+
echo "Loading cached images..."
35+
for f in /tmp/docker-cache/*.tar.zst; do
36+
zstd -d -c "$f" | docker load &
37+
done
38+
wait
39+
docker images
40+
41+
- name: Pull and save Docker images
42+
if: steps.docker-cache.outputs.cache-hit != 'true'
43+
shell: bash
44+
env:
45+
IMAGES_INPUT: ${{ inputs.images }}
46+
run: |
47+
mkdir -p /tmp/docker-cache
48+
49+
echo "Pulling images in parallel..."
50+
for img in $IMAGES_INPUT; do
51+
docker pull "$img" &
52+
done
53+
wait
54+
55+
echo "Saving images with zstd compression..."
56+
for img in $IMAGES_INPUT; do
57+
# Create filename from image name (replace special chars)
58+
filename=$(echo "$img" | tr '/:' '_')
59+
docker save "$img" | zstd -T0 -3 > "/tmp/docker-cache/${filename}.tar.zst" &
60+
done
61+
wait
62+
63+
echo "Cache size:"
64+
du -sh /tmp/docker-cache/

.github/actions/setup-ci-compose/action.yml

Lines changed: 0 additions & 55 deletions
This file was deleted.

.github/workflows/backend-ci.yml

Lines changed: 143 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,64 @@ on:
1515
- 'docker-compose.ci.yaml'
1616
workflow_dispatch:
1717

18+
# Pin image versions for cache key consistency
19+
env:
20+
MONGO_IMAGE: mongo:8.0
21+
REDIS_IMAGE: redis:7-alpine
22+
KAFKA_IMAGE: apache/kafka:3.9.0
23+
SCHEMA_REGISTRY_IMAGE: confluentinc/cp-schema-registry:7.5.0
24+
1825
jobs:
26+
unit:
27+
name: Unit Tests
28+
runs-on: ubuntu-latest
29+
30+
steps:
31+
- uses: actions/checkout@v6
32+
33+
- name: Set up uv
34+
uses: astral-sh/setup-uv@v7
35+
with:
36+
enable-cache: true
37+
cache-dependency-glob: "backend/uv.lock"
38+
39+
- name: Install Python dependencies
40+
run: |
41+
cd backend
42+
uv python install 3.12
43+
uv sync --frozen
44+
45+
- name: Run unit tests
46+
timeout-minutes: 5
47+
run: |
48+
cd backend
49+
uv run pytest tests/unit -v -rs \
50+
--cov=app \
51+
--cov-report=xml --cov-report=term
52+
53+
- name: Upload coverage to Codecov
54+
uses: codecov/codecov-action@v5
55+
if: always()
56+
with:
57+
token: ${{ secrets.CODECOV_TOKEN }}
58+
files: backend/coverage.xml
59+
flags: backend-unit
60+
name: backend-unit-coverage
61+
fail_ci_if_error: false
62+
verbose: true
63+
1964
integration:
2065
name: Integration Tests
2166
runs-on: ubuntu-latest
67+
2268
steps:
2369
- uses: actions/checkout@v6
2470

71+
- name: Cache and load Docker images
72+
uses: ./.github/actions/docker-cache
73+
with:
74+
images: ${{ env.MONGO_IMAGE }} ${{ env.REDIS_IMAGE }} ${{ env.KAFKA_IMAGE }} ${{ env.SCHEMA_REGISTRY_IMAGE }}
75+
2576
- name: Set up uv
2677
uses: astral-sh/setup-uv@v7
2778
with:
@@ -34,95 +85,137 @@ jobs:
3485
uv python install 3.12
3586
uv sync --frozen
3687
37-
- name: Setup Docker Buildx
38-
uses: docker/setup-buildx-action@v3
88+
- name: Start infrastructure services
89+
run: |
90+
docker compose -f docker-compose.ci.yaml up -d --wait --wait-timeout 120
91+
docker compose -f docker-compose.ci.yaml ps
3992
40-
- name: Setup Kubernetes (k3s)
93+
- name: Run integration tests
94+
timeout-minutes: 10
95+
env:
96+
MONGO_ROOT_USER: root
97+
MONGO_ROOT_PASSWORD: rootpassword
98+
MONGODB_HOST: 127.0.0.1
99+
MONGODB_PORT: 27017
100+
MONGODB_URL: mongodb://root:[email protected]:27017/?authSource=admin
101+
KAFKA_BOOTSTRAP_SERVERS: localhost:9092
102+
SCHEMA_REGISTRY_URL: http://localhost:8081
103+
REDIS_HOST: localhost
104+
REDIS_PORT: 6379
105+
SCHEMA_SUBJECT_PREFIX: "ci.${{ github.run_id }}."
41106
run: |
42-
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik --tls-san host.docker.internal" sh -
43-
mkdir -p /home/runner/.kube
44-
sudo k3s kubectl config view --raw > /home/runner/.kube/config
45-
sudo chmod 600 /home/runner/.kube/config
46-
export KUBECONFIG=/home/runner/.kube/config
47-
timeout 90 bash -c 'until sudo k3s kubectl cluster-info; do sleep 5; done'
107+
cd backend
108+
uv run pytest tests/integration -v -rs \
109+
--ignore=tests/integration/k8s \
110+
--cov=app \
111+
--cov-report=xml --cov-report=term
48112
49-
- name: Create kubeconfig for CI Docker containers
113+
- name: Upload coverage to Codecov
114+
uses: codecov/codecov-action@v5
115+
if: always()
116+
with:
117+
token: ${{ secrets.CODECOV_TOKEN }}
118+
files: backend/coverage.xml
119+
flags: backend-integration
120+
name: backend-integration-coverage
121+
fail_ci_if_error: false
122+
verbose: true
123+
124+
- name: Collect logs
125+
if: failure()
50126
run: |
51-
# Copy real k3s kubeconfig with valid credentials, but change server address
52-
# from 127.0.0.1 to host.docker.internal for Docker container networking
53-
# (k3s was started with --tls-san host.docker.internal so the cert is valid)
54-
sed 's|https://127.0.0.1:6443|https://host.docker.internal:6443|g' \
55-
/home/runner/.kube/config > backend/kubeconfig.yaml
56-
chmod 644 backend/kubeconfig.yaml
57-
58-
- name: Setup CI Compose
59-
uses: ./.github/actions/setup-ci-compose
127+
mkdir -p logs
128+
docker compose -f docker-compose.ci.yaml logs > logs/docker-compose.log 2>&1
129+
docker compose -f docker-compose.ci.yaml logs kafka > logs/kafka.log 2>&1
130+
docker compose -f docker-compose.ci.yaml logs schema-registry > logs/schema-registry.log 2>&1
131+
132+
- name: Upload logs
133+
if: failure()
134+
uses: actions/upload-artifact@v6
60135
with:
61-
kubeconfig-path: /home/runner/.kube/config
136+
name: backend-logs
137+
path: logs/
138+
139+
e2e:
140+
name: E2E Tests
141+
runs-on: ubuntu-latest
142+
143+
steps:
144+
- uses: actions/checkout@v6
62145

63-
- name: Build services
64-
uses: docker/bake-action@v6
146+
- name: Cache and load Docker images
147+
uses: ./.github/actions/docker-cache
65148
with:
66-
source: .
67-
files: docker-compose.ci.yaml
68-
load: true
69-
set: |
70-
*.cache-from=type=gha,scope=buildkit-${{ github.repository }}-${{ github.ref_name }}
71-
*.cache-from=type=gha,scope=buildkit-${{ github.repository }}-main
72-
*.cache-to=type=gha,mode=max,scope=buildkit-${{ github.repository }}-${{ github.ref_name }}
73-
*.pull=true
74-
env:
75-
BUILDKIT_PROGRESS: plain
149+
images: ${{ env.MONGO_IMAGE }} ${{ env.REDIS_IMAGE }} ${{ env.KAFKA_IMAGE }} ${{ env.SCHEMA_REGISTRY_IMAGE }}
150+
151+
- name: Set up uv
152+
uses: astral-sh/setup-uv@v7
153+
with:
154+
enable-cache: true
155+
cache-dependency-glob: "backend/uv.lock"
76156

77-
- name: Start services
157+
- name: Install Python dependencies
78158
run: |
79-
docker compose -f docker-compose.ci.yaml up -d --remove-orphans
80-
docker compose -f docker-compose.ci.yaml ps
159+
cd backend
160+
uv python install 3.12
161+
uv sync --frozen
81162
82-
- name: Wait for backend
163+
- name: Start infrastructure services
83164
run: |
84-
curl --retry 60 --retry-delay 5 --retry-all-errors -ksf https://127.0.0.1:443/api/v1/health/live
165+
docker compose -f docker-compose.ci.yaml up -d --wait --wait-timeout 120
85166
docker compose -f docker-compose.ci.yaml ps
86-
kubectl get pods -A -o wide
87167
88-
- name: Run integration tests
168+
- name: Setup Kubernetes (k3s)
169+
run: |
170+
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -
171+
mkdir -p /home/runner/.kube
172+
sudo k3s kubectl config view --raw > /home/runner/.kube/config
173+
sudo chmod 600 /home/runner/.kube/config
174+
export KUBECONFIG=/home/runner/.kube/config
175+
timeout 90 bash -c 'until sudo k3s kubectl cluster-info; do sleep 5; done'
176+
kubectl create namespace integr8scode --dry-run=client -o yaml | kubectl apply -f -
177+
178+
- name: Run E2E tests
89179
timeout-minutes: 10
90180
env:
91-
BACKEND_BASE_URL: https://127.0.0.1:443
92181
MONGO_ROOT_USER: root
93182
MONGO_ROOT_PASSWORD: rootpassword
94-
MONGODB_HOST: 127.0.0.1
95-
MONGODB_PORT: 27017
96183
MONGODB_URL: mongodb://root:[email protected]:27017/?authSource=admin
184+
KAFKA_BOOTSTRAP_SERVERS: localhost:9092
185+
SCHEMA_REGISTRY_URL: http://localhost:8081
186+
REDIS_HOST: localhost
187+
REDIS_PORT: 6379
97188
SCHEMA_SUBJECT_PREFIX: "ci.${{ github.run_id }}."
189+
KUBECONFIG: /home/runner/.kube/config
190+
K8S_NAMESPACE: integr8scode
98191
run: |
99192
cd backend
100-
uv run pytest tests/integration -v -rs --cov=app --cov-branch --cov-report=xml --cov-report=term
193+
uv run pytest tests/integration/k8s -v -rs \
194+
--cov=app \
195+
--cov-report=xml --cov-report=term
101196
102197
- name: Upload coverage to Codecov
103198
uses: codecov/codecov-action@v5
104199
if: always()
105200
with:
106201
token: ${{ secrets.CODECOV_TOKEN }}
107202
files: backend/coverage.xml
108-
flags: backend
109-
name: backend-coverage
203+
flags: backend-e2e
204+
name: backend-e2e-coverage
110205
fail_ci_if_error: false
111206
verbose: true
112207

113208
- name: Collect logs
114209
if: failure()
115210
run: |
116211
mkdir -p logs
117-
docker compose -f docker-compose.ci.yaml logs > logs/docker-compose.log
118-
docker compose -f docker-compose.ci.yaml logs backend > logs/backend.log
119-
docker compose -f docker-compose.ci.yaml logs mongo > logs/mongo.log
120-
kubectl get events --sort-by='.metadata.creationTimestamp' > logs/k8s-events.log 2>&1 || true
212+
docker compose -f docker-compose.ci.yaml logs > logs/docker-compose.log 2>&1
213+
kubectl get events --sort-by='.metadata.creationTimestamp' -A > logs/k8s-events.log 2>&1 || true
121214
kubectl describe pods -A > logs/k8s-describe-pods.log 2>&1 || true
122215
123216
- name: Upload logs
124217
if: failure()
125218
uses: actions/upload-artifact@v6
126219
with:
127-
name: backend-logs
220+
name: k8s-logs
128221
path: logs/

0 commit comments

Comments
 (0)