Make lint report instead of rewriting, and stop verify guessing the domain
`make lint-frontend` was `biome check --write --unsafe ./` — a lint target that reformatted the whole tree rather than checking it, which is why every parallel change in this repo has had to work around it. `lint` checks now and a new `format` writes. The pre-commit hook and CI needed no edit at all: both call `bun run lint`, so they became checks the moment its meaning changed. `app/Makefile` assigned DOMAIN from .env, and a plain assignment beats an inherited environment variable and is not exported — so `cd app && make dev-local` served localhost while the same checkout's tests targeted the configured domain. `export DOMAIN ?=` gives the lattice that was intended: command line, then environment, then .env. Alongside: the backend's htmlcov bind mount created that directory as root, so `make test-backend` died on the coverage step after every test had passed, which reads like a test failure and is not one. The alerts screen's copy of ALERTING_EVENTS is now checked by a test rather than trusted. And the shard comment claimed two spec files where there are nine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
This commit is contained in:
@@ -37,9 +37,10 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# Two spec files (tests/flows.spec.ts, tests/admin.spec.ts) plus
|
# Nine spec files under tests/, plus tests/auth.setup.ts, which every
|
||||||
# tests/auth.setup.ts, which every shard reruns as its setup project.
|
# shard reruns as its setup project. Split two ways: each shard pays a
|
||||||
# More shards than that would only pay container startup twice over.
|
# full compose stack's startup, so more of them would spend more time
|
||||||
|
# booting than testing.
|
||||||
shardIndex: [1, 2]
|
shardIndex: [1, 2]
|
||||||
shardTotal: [2]
|
shardTotal: [2]
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
.PHONY: dev-utils dev dev-local up down update install dev-backend dev-frontend \
|
.PHONY: dev-utils dev dev-local up down update install dev-backend dev-frontend \
|
||||||
generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
|
generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
|
||||||
lint-frontend umami clean help
|
lint-frontend format-frontend umami clean help
|
||||||
|
|
||||||
COMPOSE_ROOT := $(CURDIR)
|
COMPOSE_ROOT := $(CURDIR)
|
||||||
# Explicit project name keeps this stack isolated from the sibling website
|
# Explicit project name keeps this stack isolated from the sibling website
|
||||||
@@ -133,7 +133,7 @@ test-backend: ## Run backend tests (pytest + coverage)
|
|||||||
# deployment carries the deployment's domain, and *.fluksio.com resolves to the
|
# deployment carries the deployment's domain, and *.fluksio.com resolves to the
|
||||||
# live instance from here — which is why the run below maps both names onto the
|
# live instance from here — which is why the run below maps both names onto the
|
||||||
# local Traefik by address and never lets DNS decide.
|
# local Traefik by address and never lets DNS decide.
|
||||||
DOMAIN = $(shell sed -n 's/^DOMAIN=//p' $(COMPOSE_ROOT)/.env | head -1)
|
export DOMAIN ?= $(shell sed -n 's/^DOMAIN=//p' $(COMPOSE_ROOT)/.env | head -1)
|
||||||
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
|
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
|
||||||
|
|
||||||
test-frontend: ## Run frontend tests (Playwright e2e) against the local stack
|
test-frontend: ## Run frontend tests (Playwright e2e) against the local stack
|
||||||
@@ -174,6 +174,9 @@ lint-backend: ## Lint backend with ruff + mypy
|
|||||||
lint-frontend: ## Lint frontend with biome
|
lint-frontend: ## Lint frontend with biome
|
||||||
cd frontend && bun run lint
|
cd frontend && bun run lint
|
||||||
|
|
||||||
|
format-frontend: ## Apply biome's fixes to the frontend (what `lint-frontend` only reports)
|
||||||
|
cd frontend && bun run format
|
||||||
|
|
||||||
# ── Cleanup ───────────────────────────────────────────────────────
|
# ── Cleanup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
clean: ## Remove build artifacts and caches
|
clean: ## Remove build artifacts and caches
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""Alerting: failing loudly once, not thirty-six thousand times."""
|
"""Alerting: failing loudly once, not thirty-six thousand times."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.flow.alerts import (
|
from app.flow.alerts import (
|
||||||
|
ALERTING_EVENTS,
|
||||||
FLAP_THRESHOLD,
|
FLAP_THRESHOLD,
|
||||||
RATE_LIMIT,
|
RATE_LIMIT,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -230,3 +233,17 @@ def test_a_dashboard_channel_without_a_message_says_so():
|
|||||||
asyncio.run(
|
asyncio.run(
|
||||||
alerts.send(channel, Alert(title="t", body="b"), raise_on_error=True)
|
alerts.send(channel, Alert(title="t", body="b"), raise_on_error=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ALERTS_ROUTE = Path(__file__).parents[3] / "frontend/src/routes/_layout/alerts.tsx"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not ALERTS_ROUTE.exists(), reason="no frontend in this checkout")
|
||||||
|
def test_the_alerts_screen_offers_every_alerting_event():
|
||||||
|
"""The chooser lists the events by hand, so it can drift out of this set.
|
||||||
|
|
||||||
|
Only the ids have to agree — the labels beside them are UI copy.
|
||||||
|
"""
|
||||||
|
block = re.search(r"const EVENTS[^=]*= \[(.*?)\n\]", ALERTS_ROUTE.read_text(), re.S)
|
||||||
|
assert block, "the EVENTS list moved — this test needs following"
|
||||||
|
assert set(re.findall(r'\["(\w+)"', block.group(1))) == ALERTING_EVENTS
|
||||||
|
|||||||
+12
-9
@@ -14,9 +14,9 @@ SPA, the API and the marketing site answer on one port. `make status` lists the
|
|||||||
`make down` stops everything.
|
`make down` stops everything.
|
||||||
|
|
||||||
The hostname comes from `DOMAIN` in `app/.env`, which `scripts/setup.sh` copies out of the
|
The hostname comes from `DOMAIN` in `app/.env`, which `scripts/setup.sh` copies out of the
|
||||||
root `.env` — change it there and re-run `make init`. `make dev DOMAIN=…` at the root does
|
root `.env` — change it there and re-run `make init`. `app/Makefile` takes that value as a
|
||||||
*not* reach this stack: `app/Makefile` assigns `DOMAIN` from `app/.env`, and a makefile
|
default only (`export DOMAIN ?= …`), so `make dev DOMAIN=…` overrides it and the override
|
||||||
assignment overrides an inherited environment variable.
|
reaches the recipes.
|
||||||
|
|
||||||
**App only.** `cd app && make dev` starts this stack on its own Traefik with all host ports
|
**App only.** `cd app && make dev` starts this stack on its own Traefik with all host ports
|
||||||
published, in the foreground. `make dev-utils` starts just db, adminer, proxy, mailcatcher
|
published, in the foreground. `make dev-utils` starts just db, adminer, proxy, mailcatcher
|
||||||
@@ -145,15 +145,18 @@ run when either origin resolves outside loopback or the private ranges, because
|
|||||||
creates and deletes flows, dashboards and users. `PLAYWRIGHT_ALLOW_PUBLIC=1` overrides it.
|
creates and deletes flows, dashboards and users. `PLAYWRIGHT_ALLOW_PUBLIC=1` overrides it.
|
||||||
|
|
||||||
**Visual check.** From the root, `make verify` logs in and screenshots both themes into
|
**Visual check.** From the root, `make verify` logs in and screenshots both themes into
|
||||||
`app/frontend/screenshots/{light,dark}/`; `make verify-docker` does the same from the
|
`app/frontend/screenshots/{light,dark}/`. It is an alias for `make verify-docker`: the run
|
||||||
Playwright container when the host lacks the browser libraries. Both default to `localhost`
|
always happens in the Playwright container, which pins the browser by reading
|
||||||
rather than reading `.env`, so on a checkout configured for a deployment name the domain the
|
`frontend/package.json`. Running Playwright on the host is unsupported — the workspace pins a
|
||||||
stack actually serves: `make verify DOMAIN=example.com`.
|
browser revision whose install is incomplete. The domain comes from `.env`, and
|
||||||
|
`make verify DOMAIN=example.com` still wins.
|
||||||
|
|
||||||
## Lint, hooks and the generated client
|
## Lint, hooks and the generated client
|
||||||
|
|
||||||
`make lint` runs ruff, mypy and biome. `make hooks` at the root installs the pre-commit
|
`make lint` runs ruff, mypy and biome, and only reports — it is what the pre-commit hook and
|
||||||
hooks in both stacks; `.pre-commit-config.yaml` holds them. One is worth knowing about:
|
CI run, so neither rewrites the tree. `make format-frontend` is the writing half, applying
|
||||||
|
biome's fixes. `make hooks` at the root installs the pre-commit hooks in both stacks;
|
||||||
|
`.pre-commit-config.yaml` holds them. One is worth knowing about:
|
||||||
`generate-frontend-sdk` regenerates the SPA's API client from the backend's OpenAPI schema
|
`generate-frontend-sdk` regenerates the SPA's API client from the backend's OpenAPI schema
|
||||||
whenever anything under `backend/` changes, so a route change reaches the frontend without
|
whenever anything under `backend/` changes, so a route change reaches the frontend without
|
||||||
being asked. `make generate-client` forces it.
|
being asked. `make generate-client` forces it.
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ services:
|
|||||||
- path: ../backend/pyproject.toml
|
- path: ../backend/pyproject.toml
|
||||||
action: rebuild
|
action: rebuild
|
||||||
volumes:
|
volumes:
|
||||||
- ../backend/htmlcov:/app/backend/htmlcov
|
|
||||||
# What makes --reload above mean anything in the detached flow (`make dev`,
|
# What makes --reload above mean anything in the detached flow (`make dev`,
|
||||||
# `make dev-local`): it starts the stack with `up -d`, which never runs the
|
# `make dev-local`): it starts the stack with `up -d`, which never runs the
|
||||||
# sync above — only `docker compose watch` does. Without the mount the
|
# sync above — only `docker compose watch` does. Without the mount the
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -p tsconfig.build.json && vite build",
|
"build": "tsc -p tsconfig.build.json && vite build",
|
||||||
"lint": "biome check --write --unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
"lint": "biome check --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
||||||
|
"format": "biome check --write --unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"generate-client": "openapi-ts",
|
"generate-client": "openapi-ts",
|
||||||
"test": "bunx playwright test",
|
"test": "bunx playwright test",
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ const alertsKey = ["alerts", "config"]
|
|||||||
const SECTION =
|
const SECTION =
|
||||||
"text-xs font-medium uppercase tracking-[0.5px] text-muted-foreground"
|
"text-xs font-medium uppercase tracking-[0.5px] text-muted-foreground"
|
||||||
|
|
||||||
/** What the engine can alert on. Mirrors `ALERTING_EVENTS` in the backend; a
|
/** What the engine can alert on. Mirrors `ALERTING_EVENTS` in the backend, and
|
||||||
* rule with none of them ticked still covers everything, including any event
|
* `backend/tests/flow/test_alerts.py` reads this list to fail when the two
|
||||||
* added there later. */
|
* drift apart. A rule with none of them ticked still covers everything,
|
||||||
|
* including any event added there later. */
|
||||||
const EVENTS: [string, string][] = [
|
const EVENTS: [string, string][] = [
|
||||||
["node_error", "A node failed"],
|
["node_error", "A node failed"],
|
||||||
["node_health", "A connection dropped"],
|
["node_health", "A connection dropped"],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --filter frontend dev",
|
"dev": "bun run --filter frontend dev",
|
||||||
"lint": "bun run --filter frontend lint",
|
"lint": "bun run --filter frontend lint",
|
||||||
|
"format": "bun run --filter frontend format",
|
||||||
"test": "bun run --filter frontend test",
|
"test": "bun run --filter frontend test",
|
||||||
"test:ui": "bun run --filter frontend test:ui"
|
"test:ui": "bun run --filter frontend test:ui"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ uv run python -c "import app.main; import json; print(json.dumps(app.main.app.op
|
|||||||
cd ..
|
cd ..
|
||||||
mv openapi.json frontend/
|
mv openapi.json frontend/
|
||||||
bun run --filter frontend generate-client
|
bun run --filter frontend generate-client
|
||||||
bun run lint
|
bun run format
|
||||||
|
|||||||
Reference in New Issue
Block a user