Add configuration files and update project documentation

- Introduced .editorconfig for consistent coding styles across the project.
- Added .golangci.yml for Go linting configuration.
- Updated AGENTS.md to clarify project structure and components.
- Enhanced CONTRIBUTING.md with Makefile usage for common tasks.
- Updated Dockerfiles to use Go 1.24 and improved build instructions.
- Refined README.md and deployment documentation for clarity.
- Added testing documentation in testing.md for backend and frontend tests.
- Introduced Makefile for streamlined development commands and tasks.
This commit is contained in:
2026-03-01 01:51:47 +03:00
parent 0466ff3087
commit 6529d7370e
92 changed files with 13411 additions and 8438 deletions

View File

@@ -1,58 +1,77 @@
# HTTP API
API доступно по префиксу `/map/api/`. Для запросов, требующих авторизации, используется cookie `session` (устанавливается при логине).
The API is available under the `/map/api/` prefix. Requests requiring authentication use a `session` cookie (set on login).
## Авторизация
## Authentication
- **POST /map/api/login** — вход. Тело: `{"user":"...","pass":"..."}`. При успехе возвращается JSON с данными пользователя и выставляется cookie сессии. При первом запуске возможен bootstrap: логин `admin` и пароль из `HNHMAP_BOOTSTRAP_PASSWORD` создают первого админа. Для пользователей, созданных через OAuth (без пароля), возвращается 401 с `{"error":"Use OAuth to sign in"}`.
- **GET /map/api/me** — текущий пользователь (по сессии). Ответ: `username`, `auths`, при необходимости `tokens`, `prefix`.
- **POST /map/api/logout** — выход (инвалидация сессии).
- **GET /map/api/setup** — проверка, нужна ли первичная настройка. Ответ: `{"setupRequired": true|false}`.
- **POST /map/api/login** — sign in. Body: `{"user":"...","pass":"..."}`. On success returns JSON with user data and sets a session cookie. On first run, bootstrap is available: logging in as `admin` with the password from `HNHMAP_BOOTSTRAP_PASSWORD` creates the first admin user. For users created via OAuth (no password), returns 401 with `{"error":"Use OAuth to sign in"}`.
- **GET /map/api/me** — current user (by session). Response: `username`, `auths`, and optionally `tokens`, `prefix`.
- **POST /map/api/logout** — sign out (invalidates the session).
- **GET /map/api/setup** — check if initial setup is needed. Response: `{"setupRequired": true|false}`.
### OAuth
- **GET /map/api/oauth/providers** — список настроенных OAuth-провайдеров. Ответ: `["google", ...]`.
- **GET /map/api/oauth/{provider}/login** — редирект на страницу авторизации провайдера. Query: `redirect` — путь для редиректа после успешного входа (например `/profile`).
- **GET /map/api/oauth/{provider}/callback** — callback от провайдера (вызывается автоматически). Обменивает `code` на токены, создаёт или находит пользователя, создаёт сессию и редиректит на `/profile` или `redirect` из state.
- **GET /map/api/oauth/providers** — list of configured OAuth providers. Response: `["google", ...]`.
- **GET /map/api/oauth/{provider}/login** — redirect to the provider's authorization page. Query: `redirect` — path to redirect to after successful login (e.g. `/profile`).
- **GET /map/api/oauth/{provider}/callback** — callback from the provider (called automatically). Exchanges the `code` for tokens, creates or finds the user, creates a session, and redirects to `/profile` or the `redirect` from state.
## Кабинет
## User account
- **POST /map/api/me/tokens** — сгенерировать новый токен загрузки (требуется право `upload`). Ответ: `{"tokens": ["...", ...]}`.
- **POST /map/api/me/password** — сменить пароль. Тело: `{"pass":"..."}`.
- **POST /map/api/me/tokens** — generate a new upload token (requires `upload` permission). Response: `{"tokens": ["...", ...]}`.
- **POST /map/api/me/password** — change password. Body: `{"pass":"..."}`.
## Карта и данные
## Map data
- **GET /map/api/config** — конфиг для клиента (title, auths). Требуется сессия.
- **GET /map/api/v1/characters** — список персонажей на карте (требуется право `map` и при необходимости `markers`).
- **GET /map/api/v1/markers** — маркеры (требуется право `map` и при необходимости `markers`).
- **GET /map/api/maps** — список карт (с учётом прав и скрытых карт).
- **GET /map/api/config** — client configuration (title, auths). Requires a session.
- **GET /map/api/v1/characters** — list of characters on the map (requires `map` permission; `markers` permission needed to see data).
- **GET /map/api/v1/markers** — markers (requires `map` permission; `markers` permission needed to see data).
- **GET /map/api/maps** — list of maps (filtered by permissions and hidden status).
## Админ (все эндпоинты ниже требуют право `admin`)
## Admin (all endpoints below require `admin` permission)
- **GET /map/api/admin/users** — список имён пользователей.
- **POST /map/api/admin/users** — создать/обновить пользователя. Тело: `{"user":"...","pass":"...","auths":["admin","map",...]}`.
- **GET /map/api/admin/users/:name** — данные пользователя.
- **DELETE /map/api/admin/users/:name** — удалить пользователя.
- **GET /map/api/admin/settings** — настройки (prefix, defaultHide, title).
- **POST /map/api/admin/settings** — сохранить настройки. Тело: `{"prefix":"...","defaultHide":true|false,"title":"..."}` (поля опциональны).
- **GET /map/api/admin/maps** — список карт для админки.
- **POST /map/api/admin/maps/:id** — обновить карту (name, hidden, priority).
- **POST /map/api/admin/maps/:id/toggle-hidden** — переключить скрытие карты.
- **POST /map/api/admin/wipe** — очистить гриды, маркеры, тайлы, карты в БД.
- **POST /map/api/admin/rebuildZooms** — пересобрать зум-уровни тайлов.
- **GET /map/api/admin/export** — скачать экспорт данных (ZIP).
- **POST /map/api/admin/merge** — загрузить и применить merge (ZIP с гридами и маркерами).
- **GET /map/api/admin/wipeTile** — удалить тайл. Query: `map`, `x`, `y`.
- **GET /map/api/admin/setCoords** — сдвинуть координаты гридов. Query: `map`, `fx`, `fy`, `tx`, `ty`.
- **GET /map/api/admin/hideMarker** — скрыть маркер. Query: `id`.
- **GET /map/api/admin/users** — list of usernames.
- **POST /map/api/admin/users** — create or update a user. Body: `{"user":"...","pass":"...","auths":["admin","map",...]}`.
- **GET /map/api/admin/users/:name** — user data.
- **DELETE /map/api/admin/users/:name** — delete a user.
- **GET /map/api/admin/settings** — settings (prefix, defaultHide, title).
- **POST /map/api/admin/settings** — save settings. Body: `{"prefix":"...","defaultHide":true|false,"title":"..."}` (all fields optional).
- **GET /map/api/admin/maps** — list of maps for the admin panel.
- **POST /map/api/admin/maps/:id** — update a map (name, hidden, priority).
- **POST /map/api/admin/maps/:id/toggle-hidden** — toggle map visibility.
- **POST /map/api/admin/wipe** — wipe grids, markers, tiles, and maps from the database.
- **POST /map/api/admin/rebuildZooms** — rebuild tile zoom levels.
- **GET /map/api/admin/export** — download data export (ZIP).
- **POST /map/api/admin/merge** — upload and apply a merge (ZIP with grids and markers).
- **GET /map/api/admin/wipeTile** — delete a tile. Query: `map`, `x`, `y`.
- **GET /map/api/admin/setCoords** — shift grid coordinates. Query: `map`, `fx`, `fy`, `tx`, `ty`.
- **GET /map/api/admin/hideMarker** — hide a marker. Query: `id`.
## Коды ответов
## Game client
- **200** — успех.
- **400** — неверный запрос (метод, тело, параметры).
- **401** — не авторизован (нет или недействительная сессия).
- **403** — нет прав.
- **404** — не найдено.
- **500** — внутренняя ошибка.
The game client (e.g. Purus Pasta) communicates via `/client/{token}/...` endpoints using token-based authentication.
Формат ошибок — текст в теле ответа или стандартные HTTP-статусы без тела.
- **GET /client/{token}/checkVersion** — check client protocol version. Query: `version`. Returns 200 if matching, 400 otherwise.
- **GET /client/{token}/locate** — get grid coordinates. Query: `gridID`. Response: `mapid;x;y`.
- **POST /client/{token}/gridUpdate** — report visible grids and receive upload requests.
- **POST /client/{token}/gridUpload** — upload a tile image (multipart).
- **POST /client/{token}/positionUpdate** — update character positions.
- **POST /client/{token}/markerUpdate** — upload markers.
## SSE (Server-Sent Events)
- **GET /map/updates** — real-time tile and merge updates. Requires a session with `map` permission. Sends `data:` messages with tile cache arrays and `event: merge` messages for map merges.
## Tile images
- **GET /map/grids/{mapid}/{zoom}/{x}_{y}.png** — tile image. Requires a session with `map` permission. Returns the tile image or a transparent 1×1 PNG if the tile does not exist.
## Response codes
- **200** — success.
- **400** — bad request (wrong method, body, or parameters).
- **401** — unauthorized (missing or invalid session).
- **403** — forbidden (insufficient permissions).
- **404** — not found.
- **500** — internal error.
Error format: JSON body `{"error": "message", "code": "CODE"}`.

View File

@@ -1,43 +1,57 @@
# Архитектура hnh-map
# Architecture of hnh-map
## Обзор
## Overview
hnh-map — сервер автомаппера для HnH: Go-бэкенд с хранилищем bbolt, сессиями и Nuxt 3 SPA по корню `/`. API, SSE и тайлы — по `/map/api/`, `/map/updates`, `/map/grids/`. Данные гридов и тайлов хранятся в каталоге `grids/` и в БД.
hnh-map is an automapper server for HnH: a Go backend with bbolt storage, sessions, and a Nuxt 3 SPA served at `/`. API, SSE, and tiles are served at `/map/api/`, `/map/updates`, `/map/grids/`. Grid and tile data is stored in the `grids/` directory and in the database.
```
┌─────────────┐ HTTP/SSE ┌──────────────────────────────────────┐
Браузер │ ◄────────────────► │ Go-сервер (cmd/hnh-map) │
│ (Nuxt SPA │ /, /login, │ • bbolt (users, sessions, grids, │
по /) │ /map/api, │ markers, tiles, maps, config) │
│ │ /map/updates, │ • Статика фронта (frontend/)
│ │ /map/grids/ │ • internal/app — вся логика
Browser │ ◄────────────────► │ Go server (cmd/hnh-map) │
│ (Nuxt SPA │ /, /login, │ • bbolt (users, sessions, grids, │
at /) │ /map/api, │ markers, tiles, maps, config) │
│ /map/updates, │ • Frontend statics (frontend/) │
│ /map/grids/ │ • internal/app — all logic
└─────────────┘ └──────────────────────────────────────┘
```
## Структура бэкенда
## Backend structure
- **cmd/hnh-map/main.go** — единственная точка входа (`package main`): парсинг флагов (`-grids`, `-port`) и переменных окружения (`HNHMAP_PORT`), открытие bbolt, запуск миграций, создание `App`, регистрация маршрутов, запуск HTTP-сервера. Пути к `frontend/` и `public/` задаются из рабочей директории при старте.
The backend follows a layered architecture: **Store → Services → Handlers**.
- **internal/app/** — пакет `app` с типом `App` и всей логикой:
- **app.go** — структура `App`, общие типы (`Character`, `Session`, `Coord`, `Position`, `Marker`, `User`, `MapInfo`, `GridData` и т.д.), регистрация маршрутов (`RegisterRoutes`), `serveSPARoot`, `CleanChars`.
- **auth.go** — сессии и авторизация: `getSession`, `deleteSession`, `saveSession`, `getUser`, `getPage`, `createSession`, `setupRequired`, `requireAdmin`.
- **api.go** — HTTP API: авторизация (login, me, logout, setup), кабинет (tokens, password), админ (users, settings, maps, wipe, rebuildZooms, export, merge), роутер `/map/api/...`.
- **handlers_redirects.go** — редирект `/logout``/login` (после удаления сессии).
- **client.go** — роутер клиента маппера (`/client/{token}/...`), `locate`.
- **client_grid.go** — `gridUpdate`, `gridUpload`, `updateZoomLevel`.
- **client_positions.go** — `updatePositions`.
- **client_markers.go** — `uploadMarkers`.
- **admin_rebuild.go** — `doRebuildZooms`.
- **admin_tiles.go** — `wipeTile`, `setCoords`.
- **admin_markers.go** — `hideMarker`.
- **admin_export.go** — `export`.
- **admin_merge.go** — `merge`.
- **map.go** — доступ к карте: `canAccessMap`, `getChars`, `getMarkers`, `getMaps`, `config`.
- **tile.go** — тайлы и гриды: `GetTile`, `SaveTile`, `watchGridUpdates` (SSE), `gridTile`, `reportMerge`.
- **topic.go** — типы `topic` и `mergeTopic` для рассылки обновлений тайлов и слияний карт.
- **migrations.go** — миграции bbolt; из main вызывается `app.RunMigrations(db)`.
- **cmd/hnh-map/main.go** — the single entry point (`package main`): parses flags (`-grids`, `-port`) and environment variables (`HNHMAP_PORT`), opens bbolt, runs migrations, creates `App`, wires services and handlers, registers routes, and starts the HTTP server. Paths to `frontend/` and `public/` are resolved relative to the working directory at startup.
Сборка из корня репозитория:
- **internal/app/** — package `app` with the `App` type and domain types:
- **app.go** — `App` struct, domain types (`Character`, `Session`, `Coord`, `Position`, `Marker`, `User`, `MapInfo`, `GridData`, etc.), SPA serving (`ServeSPARoot`), character cleanup (`CleanChars`), constants.
- **router.go** — route registration (`Router`), interface `APIHandler`.
- **topic.go** — generic `Topic[T]` pub/sub for broadcasting tile and merge updates.
- **migrations.go** — bbolt schema migrations; called from main via `RunMigrations(db)`.
- **internal/app/store/** — database access layer:
- **db.go** — `Store` struct with bucket helpers and CRUD operations for users, sessions, tokens, config, maps, grids, tiles, markers, and OAuth states.
- **buckets.go** — bucket name constants.
- **internal/app/services/** — business logic layer:
- **auth.go** — `AuthService`: authentication, sessions, password hashing, token validation, OAuth (Google) login flow.
- **map.go** — `MapService`: map data retrieval, tile save/get, zoom level processing, SSE watch, character/marker access.
- **admin.go** — `AdminService`: user management, settings, map management, wipe, tile operations.
- **client.go** — `ClientService`: game client operations (grid update, grid upload, position updates, marker uploads).
- **export.go** — `ExportService`: data export (ZIP) and merge (import).
- **internal/app/handlers/** — HTTP handlers (thin layer over services):
- **handlers.go** — `Handlers` struct (dependency container), `HandleServiceError`.
- **response.go** — JSON response helpers.
- **api.go** — top-level API router (`APIRouter`).
- **auth.go** — login, logout, me, setup, OAuth, token, and password handlers.
- **map.go** — config, characters, markers, and maps handlers.
- **client.go** — game client HTTP handlers (grid update/upload, positions, markers).
- **admin.go** — admin HTTP handlers (users, settings, maps, wipe, tile ops, export, merge).
- **tile.go** — tile image serving and SSE endpoint.
- **internal/app/apperr/** — domain error types mapped to HTTP status codes by handlers.
- **internal/app/response/** — shared JSON response utilities.
Build from the repository root:
```bash
go build -o hnh-map ./cmd/hnh-map

View File

@@ -1,30 +1,30 @@
# Настройка
# Configuration
## Переменные окружения и флаги
## Environment variables and flags
| Переменная / флаг | Описание | По умолчанию |
|-------------------|----------|--------------|
| `HNHMAP_PORT` | Порт HTTP-сервера | 8080 |
| `-port` | То же (флаг командной строки) | значение `HNHMAP_PORT` или 8080 |
| `HNHMAP_BOOTSTRAP_PASSWORD` | Пароль для первой настройки: при отсутствии пользователей вход как `admin` с этим паролем создаёт первого админа | — |
| `HNHMAP_BASE_URL` | Полный URL приложения для OAuth redirect_uri (например `https://map.example.com`). Если не задан, берётся из `Host` и `X-Forwarded-*` | — |
| Variable / flag | Description | Default |
|-----------------|-------------|---------|
| `HNHMAP_PORT` | HTTP server port | 8080 |
| `-port` | Same (command-line flag) | value of `HNHMAP_PORT` or 8080 |
| `HNHMAP_BOOTSTRAP_PASSWORD` | Password for initial setup: when no users exist, logging in as `admin` with this password creates the first admin user | — |
| `HNHMAP_BASE_URL` | Full application URL for OAuth redirect_uri (e.g. `https://map.example.com`). If not set, derived from `Host` and `X-Forwarded-*` headers | — |
| `HNHMAP_OAUTH_GOOGLE_CLIENT_ID` | Google OAuth Client ID | — |
| `HNHMAP_OAUTH_GOOGLE_CLIENT_SECRET` | Google OAuth Client Secret | — |
| `-grids` | Каталог гридов (флаг командной строки; в Docker обычно `-grids=/map`) | `grids` |
| `-grids` | Grid storage directory (command-line flag; in Docker typically `-grids=/map`) | `grids` |
Пример для первого запуска:
Example for first run:
```bash
export HNHMAP_BOOTSTRAP_PASSWORD=your-secure-password
./hnh-map -grids=./grids -port=8080
```
В Docker часто монтируют том в `/map` и запускают с `-grids=/map`.
In Docker, a volume is typically mounted at `/map` and the app is started with `-grids=/map`.
Для фронта (Nuxt) в режиме разработки:
For the frontend (Nuxt) in development mode:
| Переменная | Описание |
|------------|----------|
| `NUXT_PUBLIC_API_BASE` | Базовый путь к API (например `/map/api` при прокси к бэкенду) |
| Variable | Description |
|----------|-------------|
| `NUXT_PUBLIC_API_BASE` | Base path to the API (e.g. `/map/api` when proxying to the backend) |
См. также [.env.example](../.env.example) в корне репозитория.
See also [.env.example](../.env.example) in the repository root.

View File

@@ -1,43 +1,49 @@
# Деплой
# Deployment
## Docker
Образ собирается из репозитория. Внутри контейнера приложение слушает порт **8080** и ожидает, что каталог данных смонтирован в `/map` (база и изображения гридов).
The image is built from the repository. Inside the container the application listens on port **8080** and expects the data directory to be mounted at `/map` (database and grid images).
Пример запуска:
Example run:
```bash
docker run -v /srv/hnh-map:/map -p 80:8080 andyleap/hnh-auto-mapper:v-4
docker run -v /srv/hnh-map:/map -p 80:8080 hnh-map
```
Или с переменными:
Or with environment variables:
```bash
docker run -v /srv/hnh-map:/map -p 8080:8080 \
-e HNHMAP_PORT=8080 \
-e HNHMAP_BOOTSTRAP_PASSWORD=your-secure-password \
andyleap/hnh-auto-mapper:v-4
hnh-map
```
Рекомендуется после первой настройки убрать или не передавать `HNHMAP_BOOTSTRAP_PASSWORD`.
It is recommended to remove or stop passing `HNHMAP_BOOTSTRAP_PASSWORD` after initial setup.
To build the image locally:
```bash
docker build -t hnh-map .
```
## OAuth (Google)
Для входа через Google OAuth:
To enable login via Google OAuth:
1. Создайте проект в [Google Cloud Console](https://console.cloud.google.com/).
2. Включите «Google+ API» / «Google Identity» и создайте OAuth 2.0 Client ID (тип «Web application»).
3. В настройках клиента добавьте Authorized redirect URI: `https://your-domain.com/map/api/oauth/google/callback` (замените на ваш домен).
4. Задайте переменные окружения:
1. Create a project in [Google Cloud Console](https://console.cloud.google.com/).
2. Enable "Google+ API" / "Google Identity" and create an OAuth 2.0 Client ID (type "Web application").
3. In the client settings, add the Authorized redirect URI: `https://your-domain.com/map/api/oauth/google/callback` (replace with your domain).
4. Set the following environment variables:
- `HNHMAP_OAUTH_GOOGLE_CLIENT_ID` — Client ID
- `HNHMAP_OAUTH_GOOGLE_CLIENT_SECRET` — Client Secret
- `HNHMAP_BASE_URL`полный URL приложения (например `https://map.example.com`) для формирования redirect_uri. Если не задан, берётся из `Host` и `X-Forwarded-*` заголовков.
- `HNHMAP_BASE_URL`full application URL (e.g. `https://map.example.com`) for forming the redirect_uri. If not set, it is derived from the `Host` and `X-Forwarded-*` headers.
## Reverse proxy
Разместите сервис за nginx, Traefik, Caddy и т.п. на нужном домене. Проксируйте весь трафик на порт 8080 контейнера (или тот порт, на котором слушает приложение). Приложение отдаёт SPA по корню `/` (/, /login, /profile, /admin и т.д.), API — по `/map/api/`, SSE `/map/updates`, тайлы — `/map/grids/`.
Place the service behind nginx, Traefik, Caddy, etc. on the desired domain. Proxy all traffic to port 8080 of the container (or whichever port the application is listening on). The application serves the SPA at root `/` (/, /login, /profile, /admin, etc.), the API at `/map/api/`, SSE at `/map/updates`, and tiles at `/map/grids/`.
## Обновление и бэкапы
## Updates and backups
- При обновлении образа сохраняйте volume с `/map`: в нём лежат `grids.db` и каталоги с тайлами.
- Регулярно делайте бэкапы каталога данных (и при необходимости экспорт через админку «Export»).
- When updating the image, preserve the volume at `/map`: it contains `grids.db` and tile image directories.
- Regularly back up the data directory (and use the admin panel "Export" feature if needed).

View File

@@ -1,25 +1,25 @@
# Разработка
# Development
## Локальный запуск
## Local setup
### Бэкенд (Go)
### Backend (Go)
Из корня репозитория:
From the repository root:
```bash
go build -o hnh-map ./cmd/hnh-map
./hnh-map -grids=./grids -port=8080
```
Или без сборки:
Or without building:
```bash
go run ./cmd/hnh-map -grids=./grids -port=8080
```
Сервер будет отдавать статику из каталога `frontend/` (нужно предварительно собрать фронт, см. ниже).
The server serves static files from the `frontend/` directory (you need to build the frontend first, see below).
### Фронтенд (Nuxt)
### Frontend (Nuxt)
```bash
cd frontend-nuxt
@@ -27,27 +27,72 @@ npm install
npm run dev
```
В dev-режиме приложение доступно по корню (например `http://localhost:3000/`). Бэкенд должен быть доступен; при необходимости настройте прокси в `nuxt.config.ts` (например на `http://localhost:8080`).
In dev mode the app is available at root (e.g. `http://localhost:3000/`). The backend must be reachable; configure the proxy in `nuxt.config.ts` if needed (e.g. to `http://localhost:8080`).
### Docker Compose (разработка)
### Docker Compose (development)
```bash
docker compose -f docker-compose.dev.yml up
```
Dev-композ поднимает два сервиса:
Or using the Makefile:
- `backend` — Go API на порту `3080` (без сборки/раздачи фронтенд-статики в dev-режиме).
- `frontend` — Nuxt dev-сервер на порту `3000` с live-reload; запросы к `/map/api`, `/map/updates`, `/map/grids` проксируются на бэкенд.
```bash
make dev
```
Используйте [http://localhost:3000/](http://localhost:3000/) как основной URL для разработки интерфейса.
Порт `3080` предназначен для API и backend-эндпоинтов; корень `/` может возвращать `404` в dev-режиме — это ожидаемо.
The dev Compose setup starts two services:
### Сборка образа и prod-композ
- `backend` — Go API on port `3080` (no frontend static serving in dev mode).
- `frontend` — Nuxt dev server on port `3000` with live-reload; requests to `/map/api`, `/map/updates`, `/map/grids` are proxied to the backend.
Use [http://localhost:3000/](http://localhost:3000/) as the primary URL for UI development.
Port `3080` is for API and backend endpoints; the root `/` may return `404` in dev mode — this is expected.
### Building the image and production Compose
```bash
docker build -t hnh-map .
docker compose -f docker-compose.prod.yml up -d
```
В prod фронт собран в образ и отдаётся бэкендом из каталога `frontend/`; порт 8080.
Or using the Makefile:
```bash
make build
```
In production the frontend is built into the image and served by the backend from the `frontend/` directory; port 8080.
## Makefile targets
| Target | Description |
|--------|-------------|
| `make dev` | Start Docker Compose development environment |
| `make build` | Build production Docker image |
| `make test` | Run Go tests (`go test ./...`) |
| `make lint` | Run Go and frontend linters |
| `make fmt` | Format all code (Go + frontend) |
| `make generate-frontend` | Build frontend static output into `frontend/` |
| `make clean` | Remove build artifacts |
## Running tests
```bash
make test
```
Or directly:
```bash
go test ./...
```
For frontend tests (if configured):
```bash
cd frontend-nuxt
npm test
```
See [docs/testing.md](testing.md) for details on the test suite.

89
docs/testing.md Normal file
View File

@@ -0,0 +1,89 @@
# Testing
## Running tests
### Backend (Go)
```bash
make test
```
Or directly:
```bash
go test ./...
```
### Frontend (Vitest)
```bash
cd frontend-nuxt
npm test
```
The frontend uses [Vitest](https://vitest.dev/) with `happy-dom` as the test environment. Configuration is in `frontend-nuxt/vitest.config.ts`.
## Test structure
### Backend
Tests use the standard `testing` package with table-driven tests. Each test creates a temporary bbolt database via `t.TempDir()` so tests are fully isolated and require no external dependencies.
| File | What it covers |
|------|---------------|
| `internal/app/app_test.go` | Domain types (`Coord.Name`, `Coord.Parent`, `Auths.Has`), `Topic` pub/sub |
| `internal/app/migrations_test.go` | Migration on fresh DB, idempotency, version tracking |
| `internal/app/store/db_test.go` | All store CRUD operations (users, sessions, tokens, config, maps, grids, tiles, markers, OAuth states), context cancellation, edge cases |
| `internal/app/services/auth_test.go` | Login, session management, token validation, password changes, OAuth bootstrap |
| `internal/app/services/admin_test.go` | User CRUD, settings, map management, wipe |
| `internal/app/services/map_test.go` | Map data retrieval, characters, markers, tiles, config, grid storage, SSE watchers |
| `internal/app/services/client_test.go` | Content-type fix helper |
| `internal/app/handlers/handlers_test.go` | HTTP integration tests for all API endpoints using `httptest` (auth, admin, map data, error handling) |
### Frontend
Frontend tests are located alongside the source code in `__tests__/` directories:
| File | What it covers |
|------|---------------|
| `frontend-nuxt/composables/__tests__/useMapApi.test.ts` | Map API composable |
| `frontend-nuxt/composables/__tests__/useMapLogic.test.ts` | Map logic composable |
| `frontend-nuxt/composables/__tests__/useAppPaths.test.ts` | Application path helpers |
| `frontend-nuxt/lib/__tests__/Character.test.ts` | Character lib |
| `frontend-nuxt/lib/__tests__/Marker.test.ts` | Marker lib |
| `frontend-nuxt/lib/__tests__/UniqueList.test.ts` | UniqueList utility |
Nuxt auto-imports are mocked via `frontend-nuxt/__mocks__/nuxt-imports.ts` and the `#imports` alias in the Vitest config.
## Writing new tests
### Backend conventions
- Use table-driven tests where multiple input/output pairs make sense.
- Create a temporary database with `t.TempDir()` and `bbolt.Open()` for isolation.
- Use `t.Helper()` in test helper functions.
- Use `t.Cleanup()` to close the database after the test.
Example pattern:
```go
func TestMyFeature(t *testing.T) {
dir := t.TempDir()
db, err := bbolt.Open(filepath.Join(dir, "test.db"), 0600, nil)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
st := store.New(db)
// ... test logic ...
}
```
For handler tests, use `httptest.NewRecorder()` and `httptest.NewRequest()` to simulate HTTP requests without starting a real server.
### Frontend conventions
- Place tests in `__tests__/` directories next to the source files.
- Name test files `<source-name>.test.ts`.
- Use Vitest globals (`describe`, `it`, `expect`).