Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,37 @@ jobs:
exit 1
fi

backend-tests:
name: Backend tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for backend changes
id: changes
run: |
if [ "${{ github.event_name }}" = "push" ]; then
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "backend/")
else
CHANGED=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }})
fi
if echo "$CHANGED" | grep -q '^backend/'; then
echo "backend=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-python@v5
if: steps.changes.outputs.backend == 'true'
with:
python-version: "3.13"
cache: pip
cache-dependency-path: backend/requirements-dev.txt
- name: Run tests
if: steps.changes.outputs.backend == 'true'
working-directory: backend
run: |
pip install -r requirements-dev.txt
python -m pytest tests/ -v --cov=app --cov-report=term-missing

frontend-lint:
name: Frontend Lint & Typecheck
runs-on: ubuntu-latest
Expand Down
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,28 @@ Services start automatically with hot reload:
## Development Commands

### Testing

```bash
# Via Docker (recommended)
# Backend tests
docker compose -f docker-compose.dev.yml exec backend python -m pytest tests/ -v

# With coverage report
docker compose -f docker-compose.dev.yml exec backend python -m pytest tests/ --cov=app --cov-report=term-missing

# Frontend checks
docker compose -f docker-compose.dev.yml exec frontend npm run lint
docker compose -f docker-compose.dev.yml exec frontend npm run typecheck

# Or locally in the frontend directory
npm run lint # ESLint (must pass with zero errors)
npm run typecheck # TypeScript type checking
```

Backend tests use an in-memory SQLite database, independent of the
PostgreSQL instance used in development. Each test gets a fresh database
with empty tables. Fixtures in `backend/tests/conftest.py` provide
pre-built model instances (commits, binaries, environments, runs,
benchmark results, auth tokens) that tests can depend on as needed.
Requests go through `httpx.AsyncClient` with FastAPI's ASGI transport,
so the full request/response cycle (middleware, dependency injection,
validation) is exercised without a running server.

Both checks run in CI on pushes to `main` and on pull requests.

### Populating Mock Data
Expand Down
2 changes: 2 additions & 0 deletions backend/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
concurrency = greenlet
15 changes: 13 additions & 2 deletions backend/app/routers/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def clean_flag(flag):
models.MemrayBuildFailure.environment_id == environment_id
)
)
await db.commit()

return {
"message": "Worker run uploaded successfully",
Expand All @@ -341,8 +342,18 @@ def clean_flag(flag):
}

except IntegrityError as e:
# Handle unique constraint violation for duplicate commit+binary+environment
if "unique_commit_binary_env" in str(e).lower():
# Check for duplicate run constraint violation.
# asyncpg exposes constraint_name directly on the original exception;
# fall back to string matching for other backends (e.g. SQLite).
constraint = getattr(getattr(e, "orig", None), "constraint_name", None)
is_duplicate = (
constraint == "unique_commit_binary_env"
if constraint is not None
else "commit_sha" in str(e).lower()
and "binary_id" in str(e).lower()
and "environment_id" in str(e).lower()
)
if is_duplicate:
logger.error(
f"Upload failed: Duplicate run for commit {commit_sha[:8]}, binary '{binary_id}', environment '{environment_id}'"
)
Expand Down
3 changes: 3 additions & 0 deletions backend/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
testpaths = tests
asyncio_mode = auto
1 change: 1 addition & 0 deletions backend/requirements-dev.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-r requirements.in
pytest
pytest-asyncio
pytest-cov
117 changes: 116 additions & 1 deletion backend/requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,114 @@ click==8.3.1 \
--hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \
--hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6
# via uvicorn
coverage==7.13.4 \
--hash=sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246 \
--hash=sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459 \
--hash=sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129 \
--hash=sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6 \
--hash=sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415 \
--hash=sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf \
--hash=sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80 \
--hash=sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 \
--hash=sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0 \
--hash=sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b \
--hash=sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9 \
--hash=sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b \
--hash=sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f \
--hash=sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505 \
--hash=sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47 \
--hash=sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55 \
--hash=sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def \
--hash=sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689 \
--hash=sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012 \
--hash=sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5 \
--hash=sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3 \
--hash=sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95 \
--hash=sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9 \
--hash=sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601 \
--hash=sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997 \
--hash=sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c \
--hash=sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac \
--hash=sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c \
--hash=sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa \
--hash=sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750 \
--hash=sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3 \
--hash=sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d \
--hash=sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12 \
--hash=sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a \
--hash=sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932 \
--hash=sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356 \
--hash=sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92 \
--hash=sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148 \
--hash=sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39 \
--hash=sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634 \
--hash=sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6 \
--hash=sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72 \
--hash=sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98 \
--hash=sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef \
--hash=sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3 \
--hash=sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9 \
--hash=sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0 \
--hash=sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a \
--hash=sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9 \
--hash=sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552 \
--hash=sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc \
--hash=sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f \
--hash=sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525 \
--hash=sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940 \
--hash=sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a \
--hash=sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23 \
--hash=sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f \
--hash=sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc \
--hash=sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b \
--hash=sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056 \
--hash=sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 \
--hash=sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb \
--hash=sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a \
--hash=sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd \
--hash=sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea \
--hash=sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126 \
--hash=sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299 \
--hash=sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9 \
--hash=sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b \
--hash=sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00 \
--hash=sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf \
--hash=sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda \
--hash=sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2 \
--hash=sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5 \
--hash=sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d \
--hash=sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9 \
--hash=sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 \
--hash=sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b \
--hash=sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa \
--hash=sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092 \
--hash=sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58 \
--hash=sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea \
--hash=sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26 \
--hash=sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea \
--hash=sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9 \
--hash=sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 \
--hash=sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f \
--hash=sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0 \
--hash=sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3 \
--hash=sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256 \
--hash=sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a \
--hash=sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903 \
--hash=sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91 \
--hash=sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd \
--hash=sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505 \
--hash=sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7 \
--hash=sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0 \
--hash=sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2 \
--hash=sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a \
--hash=sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71 \
--hash=sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985 \
--hash=sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242 \
--hash=sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d \
--hash=sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af \
--hash=sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c \
--hash=sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0
# via pytest-cov
cryptography==46.0.5 \
--hash=sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72 \
--hash=sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235 \
Expand Down Expand Up @@ -368,7 +476,9 @@ packaging==26.0 \
pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via pytest
# via
# pytest
# pytest-cov
psycopg2-binary==2.9.11 \
--hash=sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f \
--hash=sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1 \
Expand Down Expand Up @@ -586,10 +696,15 @@ pytest==9.0.2 \
# via
# -r requirements-dev.in
# pytest-asyncio
# pytest-cov
pytest-asyncio==1.3.0 \
--hash=sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5 \
--hash=sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5
# via -r requirements-dev.in
pytest-cov==7.0.0 \
--hash=sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1 \
--hash=sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861
# via -r requirements-dev.in
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
Expand Down
Empty file added backend/tests/__init__.py
Empty file.
Loading
Loading