Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Python packages sit outside the Bun test graph; this runs their pytest suites
# under uv on change. Only apps/bugsink (the Bugsink R2 storage adapter) has one.
name: Python tests

on:
pull_request:
paths:
- 'apps/bugsink/**'
- '.github/workflows/python-tests.yml'

permissions:
contents: read

jobs:
pytest:
name: pytest
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version

- name: Run adapter tests
working-directory: apps/bugsink
run: bun run test:adapter
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,8 @@ todo.md
apps/web/src/styled-system/
libs/design/design-system/src/styled-system/
libs/design/styled-system/styled-system/
styled-system-studio
styled-system-studio
# python (apps/bugsink adapter)
__pycache__/
*.pyc
.pytest_cache/
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ Screens and routes are built workable-only:
- `bun install` — whole workspace (`--frozen-lockfile` in CI; `bun.lock` is committed).
- `bun run typecheck` — `turbo run typecheck`; one project via `--filter=@vers/<name>`.
- `bun run test` — `turbo run test`, each project's own runner; one project via `--filter`. Every
package runs on `bun test` — never add vitest. Postgres-backed suites need
JS/TS package runs on `bun test` — never add vitest. Postgres-backed suites need
`bun run pg:test-container:start` first. bunfig is read from cwd, not merged up, but root-invoked
`bun test <file>` still resolves jest-extended matchers from the root preload.
- `bun run lint` / `bun run lint:fix` — `turbo run codegen typegen`, then type-aware oxlint over the
Expand All @@ -323,6 +323,8 @@ Screens and routes are built workable-only:
present; a dependency knip can't see gets a `knip.json` ignore in the PR that introduces it.
- Git hooks: lefthook (`lefthook.yml`, installed by `prepare`). Pre-push tests changed files only
(`turbo run test --affected`); `LEFTHOOK=0` skips all hooks.
- Python (`apps/bugsink` only) runs `pytest` under `uv` via its `test:adapter` script, outside the
Bun graph; the `python-tests` workflow runs it on change.

## Lint policy

Expand Down
4 changes: 3 additions & 1 deletion agents/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Screens and routes are built workable-only:
- `bun install` — whole workspace (`--frozen-lockfile` in CI; `bun.lock` is committed).
- `bun run typecheck` — `turbo run typecheck`; one project via `--filter=@vers/<name>`.
- `bun run test` — `turbo run test`, each project's own runner; one project via `--filter`. Every
package runs on `bun test` — never add vitest. Postgres-backed suites need
JS/TS package runs on `bun test` — never add vitest. Postgres-backed suites need
`bun run pg:test-container:start` first. bunfig is read from cwd, not merged up, but root-invoked
`bun test <file>` still resolves jest-extended matchers from the root preload.
- `bun run lint` / `bun run lint:fix` — `turbo run codegen typegen`, then type-aware oxlint over the
Expand All @@ -178,6 +178,8 @@ Screens and routes are built workable-only:
present; a dependency knip can't see gets a `knip.json` ignore in the PR that introduces it.
- Git hooks: lefthook (`lefthook.yml`, installed by `prepare`). Pre-push tests changed files only
(`turbo run test --affected`); `LEFTHOOK=0` skips all hooks.
- Python (`apps/bugsink` only) runs `pytest` under `uv` via its `test:adapter` script, outside the
Bun graph; the `python-tests` workflow runs it on change.

## Lint policy

Expand Down
4 changes: 4 additions & 0 deletions apps/bugsink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ manual roll is `fly deploy --config apps/bugsink/fly.toml`. `bugsink_conf.py` im
image's config and overrides only the file object storage, so the rest of Bugsink's env-driven
settings stay upstream's.

The Python here (`r2_storage.py`) sits outside the Bun test graph, so `@vers/bugsink` carries a
`test:adapter` script that runs its pytest suite (moto-mocked S3) under `uv`, and the `python-tests`
workflow runs it on changes to this directory.

## Storage

Event data lives in a dedicated database in the shared Neon project (`DATABASE_URL` secret) — no app
Expand Down
9 changes: 9 additions & 0 deletions apps/bugsink/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@vers/bugsink",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"test:adapter": "uv run --with boto3==1.43.46 --with moto --with pytest pytest r2_storage_test.py"
}
}
75 changes: 75 additions & 0 deletions apps/bugsink/r2_storage_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Adapter tests for r2_storage against a mocked S3 (moto). Bugsink's ObjectStorage
base is stubbed here — the real one only raises NotImplementedError — so the
adapter imports without installing Bugsink; these exercise the concrete R2/S3
behaviour, which is the part that can break.
"""

import os
import sys
import types

import boto3
import pytest
from moto import mock_aws

_storage = types.ModuleType("files.storage")


class ObjectStorage:
def __init__(self, name, object_kind, **options):
self.name = name
self.object_kind = object_kind


_storage.ObjectStorage = ObjectStorage
sys.modules.setdefault("files", types.ModuleType("files"))
sys.modules["files.storage"] = _storage

os.environ.setdefault("R2_ENDPOINT_URL", "https://s3.amazonaws.com")
os.environ.setdefault("R2_BUCKET", "test-bucket")
os.environ.setdefault("R2_ACCESS_KEY_ID", "test-key")
os.environ.setdefault("R2_SECRET_ACCESS_KEY", "test-secret")

import r2_storage # noqa: E402


@pytest.fixture
def storage():
with mock_aws():
boto3.client("s3", region_name="us-east-1").create_bucket(Bucket="test-bucket")
yield r2_storage.R2ObjectStorage("r2", object_kind="file")


def test_write_then_read_roundtrips(storage):
with storage.open("bundle/app.js.map", "wb") as f:
f.write(b"sourcemap-bytes")
with storage.open("bundle/app.js.map", "rb") as f:
assert f.read() == b"sourcemap-bytes"


def test_exists_reflects_presence(storage):
assert storage.exists("bundle/app.js.map") is False
with storage.open("bundle/app.js.map", "wb") as f:
f.write(b"x")
assert storage.exists("bundle/app.js.map") is True


def test_list_yields_written_keys(storage):
for key in ("a", "b/c"):
with storage.open(key, "wb") as f:
f.write(b"x")
assert set(storage.list()) == {"a", "b/c"}


def test_delete_removes_the_object(storage):
with storage.open("gone", "wb") as f:
f.write(b"x")
storage.delete("gone")
assert storage.exists("gone") is False


def test_open_rejects_unknown_mode(storage):
with pytest.raises(ValueError):
with storage.open("k", "ab"):
pass
4 changes: 4 additions & 0 deletions apps/bugsink/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["//"],
"tags": ["app"]
}
6 changes: 6 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"libs/game/worldmap-core/generate-graph.ts",
".claude/**"
],
"ignoreBinaries": ["flyctl", "gh", "ladle", "op", "pulumi"],
"ignoreBinaries": ["flyctl", "gh", "ladle", "op", "pulumi", "uv"],
"rules": {
"enumMembers": "off"
},
Expand Down
Loading