feat(seed): add API test-data seeding script and make testdata target

- scripts/seed.py: stdlib HTTP client, JPEG+EXIF via optional Pillow (PNG fallback), additive-only, dry-run
- Makefile: testdata target with ALBUMS/PHOTOS/SEED/TIMEOUT and boolean flags
- README: usage section; new dev-seed-data capability spec
This commit is contained in:
iu
2026-10-11 13:27:21 +03:00
parent b1b0b4a83e
commit 36e96cd519
8 changed files with 891 additions and 6 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-11
@@ -0,0 +1,40 @@
# Proposal
## Why
Наполнить приложение тестовыми данными можно только вручную: сейчас в локальной базе
один пользователь, один альбом и 15 фото, появившихся через UI. Чтобы проверять сетку,
лайтбокс, cursor-пагинацию, асинхронную генерацию трёх превью, извлечение EXIF,
share-ссылки и SSE-события, нужны повторяемые объёмы данных. Нужен dev-инструмент,
который наполняет систему через публичный API, не трогая БД и хранилище напрямую.
## What Changes
- Новый standalone-скрипт `scripts/seed.py` на Python: ввод через переменные окружения
(базовый URL, email, пароль владельца) и параметры командной строки.
- Создание заданного числа альбомов и фотографий от одной учётки-владельца через
существующие эндпоинты (`POST /api/albums`, `POST /api/albums/:id/photos`).
- Изображения генерируются на лету: при доступной Pillow — JPEG с EXIF
(`DateTimeOriginal`, опционально GPS); при отсутствии Pillow — PNG без EXIF на stdlib.
- HTTP-клиент — только стандартная библиотека (`urllib`), без обязательных pip-зависимостей.
- Опциональные сопутствующие сущности (по флагам, по умолчанию выключены): теги,
комментарии, лайки, share-ссылки, инвайты.
- Опциональное ожидание генерации вариантов (опрос `GET /api/photos/:id` до `thumb_url`).
- Режим только-добавления: скрипт ничего не удаляет и не изменяет; повторные запуски
добавляют данные, различимые по метке прогона в названии альбома. Есть `--dry-run`.
- Секреты читаются только из окружения и не принимаются аргументами и не логируются.
## Capabilities
### New Capabilities
- `dev-seed-data`: dev-инструмент наполнения приложения тестовыми данными через публичный API.
### Modified Capabilities
<!-- Нет: поведение системы для клиента не меняется, API не расширяется; добавляется отдельный dev-инструмент. -->
## Impact
- Новый файл `scripts/seed.py`; новый раздел в `README.md` с запуском и параметрами.
- Backend, frontend, API и хранилище не меняются — используются только существующие эндпоинты.
- Опциональная внешняя зависимость Pillow (для JPEG+EXIF); ядро работает на стандартной
библиотеке. Требуется запущенный инстанс с существующим пользователем-владельцем.
@@ -0,0 +1,111 @@
# Spec Delta
## Purpose
Dev-инструмент, который наполняет запущенное приложение тестовыми данными через
публичный API: альбомы, сгенерированные фотографии и сопутствующие сущности — без
прямого доступа к базе данных и хранилищу.
## ADDED Requirements
### Requirement: Запуск и аутентификация
Скрипт SHALL получать базовый URL, email и пароль из переменных окружения и SHALL
аутентифицироваться через `POST /api/auth/login`, после чего SHALL выполнять все запросы
с Bearer-токеном владельца. Пароль SHALL не приниматься аргументами командной строки и
SHALL не выводиться в поток вывода.
#### Scenario: Успешный вход
- **WHEN** заданы корректные переменные окружения и скрипт запущен
- **THEN** скрипт получает access-токен и приступает к наполнению
#### Scenario: Неверные учётные данные
- **WHEN** вход возвращает 401
- **THEN** скрипт завершается с понятным сообщением и ненулевым кодом, не создавая данных
#### Scenario: Пароль не логируется
- **WHEN** скрипт выводит ход выполнения
- **THEN** пароль и токены в вывод не попадают
### Requirement: Создание альбомов и фотографий
Скрипт SHALL создавать заданное параметрами число альбомов и фотографий в каждом от имени
аутентифицированного владельца через `POST /api/albums` и
`POST /api/albums/:id/photos`. Названия альбомов SHALL содержать метку прогона.
#### Scenario: Наполнение альбомами и фото
- **WHEN** скрипт запущен с числом альбомов и фотографий на альбом
- **THEN** создаются указанные альбомы и фотографии, а имена альбомов помечены меткой прогона
#### Scenario: Воспроизводимость
- **WHEN** задан числовой seed
- **THEN** структура и параметры генерируемых данных детерминированы для этого seed
### Requirement: Генерация изображений с EXIF
Скрипт SHALL генерировать загружаемые изображения на лету. При доступной библиотеке Pillow
он SHALL использовать JPEG с EXIF-метаданными (время съёмки, при необходимости GPS); при
отсутствии Pillow он SHALL использовать PNG без EXIF, созданный на стандартной библиотеке.
#### Scenario: Pillow доступна
- **WHEN** библиотека Pillow импортируется
- **THEN** загружаемые файлы — JPEG с датой съёмки в EXIF
#### Scenario: Pillow отсутствует
- **WHEN** библиотека Pillow недоступна
- **THEN** скрипт продолжает работу, загружая PNG без EXIF
### Requirement: Опциональные сопутствующие сущности
Скрипт SHALL создавать теги, комментарии, лайки, share-ссылки и инвайты только при явном
включении соответствующими флагами; без флагов эти сущности SHALL не создаваться.
#### Scenario: Флаги выключены
- **WHEN** скрипт запущен без опциональных флагов
- **THEN** создаются только альбомы и фотографии
#### Scenario: Сопутствующие данные включены
- **WHEN** включены флаги тегов, комментариев, лайков, share-ссылок или инвайтов
- **THEN** соответствующие сущности создаются для части фотографий или альбомов
### Requirement: Ожидание генерации вариантов
При включённом ожидании скрипт SHALL дожидаться появления превью, опрашивая
`GET /api/photos/:id` до появления `thumb_url`, с ограничением по таймауту.
#### Scenario: Варианты сгенерированы
- **WHEN** ожидание вариантов включено и варианты появились
- **THEN** скрипт продолжает после появления `thumb_url`
#### Scenario: Таймаут ожидания
- **WHEN** варианты не появились за отведённое время
- **THEN** скрипт сообщает о таймауте и продолжает, не зависая бесконечно
### Requirement: Только-добавление и сухой прогон
Скрипт SHALL работать в режиме только-добавления: SHALL не удалять и не изменять
существующие данные. Повторные запуски SHALL добавлять новые данные, различимые по метке
прогона. При сухом прогоне скрипт SHALL сообщать план и SHALL не выполнять изменяющих
запросов.
#### Scenario: Повторный запуск
- **WHEN** скрипт запускается повторно
- **THEN** существующие данные сохраняются, а новые добавляются с новой меткой прогона
#### Scenario: Сухой прогон
- **WHEN** включён режим сухого прогона
- **THEN** скрипт не отправляет запросов, изменяющих данные
### Requirement: Минимум зависимостей
Основной функционал скрипта SHALL работать на стандартной библиотеке Python, включая
HTTP-клиент; Pillow SHALL быть необязательной зависимостью.
#### Scenario: Запуск без внешних пакетов
- **WHEN** в окружении нет сторонних пакетов
- **THEN** скрипт выполняет базовое наполнение с изображениями PNG без EXIF
### Requirement: Запуск через Make-цель
Проект SHALL предоставлять Make-цель `testdata`, которая запускает скрипт наполнения и
передаёт ему числовые и булевы параметры. Цель SHALL не дублировать логику скрипта.
#### Scenario: Передача параметров
- **WHEN** цель запущена с заданными параметрами (число альбомов и фото, seed, булевы флаги)
- **THEN** соответствующие значения и флаги передаются в скрипт наполнения
#### Scenario: Значения по умолчанию
- **WHEN** цель запущена без параметров
- **THEN** используются документированные значения по умолчанию
@@ -0,0 +1,29 @@
# Tasks
## 1. Каркас CLI и HTTP-клиент
- [x] 1.1 Создать `scripts/seed.py` с разбором аргументов (`--albums`, `--photos-per-album`, `--seed`, `--tags`, `--comments`, `--likes`, `--share-links`, `--invites`, `--wait-variants`, `--timeout`, `--dry-run`) и чтением env (`GALO_BASE_URL`, `GALO_EMAIL`, `GALO_PASSWORD`); проверка — `python3 scripts/seed.py --help` работает без сторонних пакетов.
- [x] 1.2 Реализовать HTTP-клиент на `urllib` (JSON, multipart, Bearer, разбор `{error:{code,message}}`); проверка — обработка ошибочного ответа и сборка multipart-тела проверены на мок-данных.
- [x] 1.3 Реализовать вход через `POST /api/auth/login` и обработку ошибок (401, сеть) с ненулевым кодом; проверка — запуск с неверным паролем завершается ошибкой и не создаёт данных.
## 2. Генерация изображений
- [x] 2.1 Генерировать JPEG с EXIF (время съёмки, опционально GPS) через Pillow; проверка — в ответе `GET /api/photos/:id` в `exif` присутствует `DateTimeOriginal`.
- [x] 2.2 Добавить фолбэк PNG на стандартной библиотеке (`zlib`+`struct`) при отсутствии Pillow; проверка — при недоступной Pillow скрипт загружает PNG и фото появляется в списке.
## 3. Наполнение данными
- [x] 3.1 Создавать альбомы (`POST /api/albums`) и загружать фото (`POST /api/albums/:id/photos`, multipart) с меткой прогона; проверка — данные видны в `GET /api/albums` и `GET /api/albums/:id/photos`.
- [x] 3.2 Реализовать ожидание вариантов (`--wait-variants`): опрос `GET /api/photos/:id` до появления `thumb_url` с таймаутом; проверка — при работающем воркере ожидание завершается, при остановленном — сообщает таймаут без зависания.
- [x] 3.3 Реализовать опциональные сущности по флагам: теги (`POST /photos/:id/tags`), комментарии (`POST /photos/:id/comments`), лайки (`POST /photos/:id/likes`), share-ссылки (`POST /albums/:id/share`), инвайты (`POST /albums/:id/invites`); проверка — без флагов ничего лишнего не создаётся, с флагами сущности создаются.
- [x] 3.4 Добавить итоговый отчёт и режим `--dry-run`; проверка — сухой прогон не отправляет изменяющих запросов, а отчёт печатает фактическое число созданных сущностей.
## 4. Документация и интеграционная проверка
- [x] 4.1 Описать запуск, переменные окружения и параметры в `README.md`; проверка — задокументированный запуск наполняет локальный инстанс.
- [x] 4.2 Интеграционная проверка: прогнать скрипт с флагами против локального инстанса и убедиться в наличии альбомов, фото и вариантов, а также в сохранности существующих данных (только-добавление).
## 5. Make-цель
- [x] 5.1 Добавить цель `testdata` в `Makefile`, передающую параметры (`ALBUMS`, `PHOTOS`, `SEED`, `TIMEOUT`, булевы `TAGS`/`COMMENTS`/`LIKES`/`SHARE`/`INVITES`/`WAIT`/`DRY`, а также `ARGS`) в скрипт; проверка — `make -n testdata ALBUMS=3 PHOTOS=4 TAGS=1` показывает ожидаемые флаги, `make testdata DRY=1` печатает план без запросов, а `make testdata ALBUMS=1 PHOTOS=1` создаёт данные.
- [x] 5.2 Описать `make testdata` в `README.md`; проверка — задокументированный запуск наполняет локальный инстанс.
+111
View File
@@ -0,0 +1,111 @@
# dev-seed-data Specification
## Purpose
Dev-инструмент, который наполняет запущенное приложение тестовыми данными через
публичный API: альбомы, сгенерированные фотографии и сопутствующие сущности — без
прямого доступа к базе данных и хранилищу.
## Requirements
### Requirement: Запуск и аутентификация
Скрипт SHALL получать базовый URL, email и пароль из переменных окружения и SHALL
аутентифицироваться через `POST /api/auth/login`, после чего SHALL выполнять все запросы
с Bearer-токеном владельца. Пароль SHALL не приниматься аргументами командной строки и
SHALL не выводиться в поток вывода.
#### Scenario: Успешный вход
- **WHEN** заданы корректные переменные окружения и скрипт запущен
- **THEN** скрипт получает access-токен и приступает к наполнению
#### Scenario: Неверные учётные данные
- **WHEN** вход возвращает 401
- **THEN** скрипт завершается с понятным сообщением и ненулевым кодом, не создавая данных
#### Scenario: Пароль не логируется
- **WHEN** скрипт выводит ход выполнения
- **THEN** пароль и токены в вывод не попадают
### Requirement: Создание альбомов и фотографий
Скрипт SHALL создавать заданное параметрами число альбомов и фотографий в каждом от имени
аутентифицированного владельца через `POST /api/albums` и
`POST /api/albums/:id/photos`. Названия альбомов SHALL содержать метку прогона.
#### Scenario: Наполнение альбомами и фото
- **WHEN** скрипт запущен с числом альбомов и фотографий на альбом
- **THEN** создаются указанные альбомы и фотографии, а имена альбомов помечены меткой прогона
#### Scenario: Воспроизводимость
- **WHEN** задан числовой seed
- **THEN** структура и параметры генерируемых данных детерминированы для этого seed
### Requirement: Генерация изображений с EXIF
Скрипт SHALL генерировать загружаемые изображения на лету. При доступной библиотеке Pillow
он SHALL использовать JPEG с EXIF-метаданными (время съёмки, при необходимости GPS); при
отсутствии Pillow он SHALL использовать PNG без EXIF, созданный на стандартной библиотеке.
#### Scenario: Pillow доступна
- **WHEN** библиотека Pillow импортируется
- **THEN** загружаемые файлы — JPEG с датой съёмки в EXIF
#### Scenario: Pillow отсутствует
- **WHEN** библиотека Pillow недоступна
- **THEN** скрипт продолжает работу, загружая PNG без EXIF
### Requirement: Опциональные сопутствующие сущности
Скрипт SHALL создавать теги, комментарии, лайки, share-ссылки и инвайты только при явном
включении соответствующими флагами; без флагов эти сущности SHALL не создаваться.
#### Scenario: Флаги выключены
- **WHEN** скрипт запущен без опциональных флагов
- **THEN** создаются только альбомы и фотографии
#### Scenario: Сопутствующие данные включены
- **WHEN** включены флаги тегов, комментариев, лайков, share-ссылок или инвайтов
- **THEN** соответствующие сущности создаются для части фотографий или альбомов
### Requirement: Ожидание генерации вариантов
При включённом ожидании скрипт SHALL дожидаться появления превью, опрашивая
`GET /api/photos/:id` до появления `thumb_url`, с ограничением по таймауту.
#### Scenario: Варианты сгенерированы
- **WHEN** ожидание вариантов включено и варианты появились
- **THEN** скрипт продолжает после появления `thumb_url`
#### Scenario: Таймаут ожидания
- **WHEN** варианты не появились за отведённое время
- **THEN** скрипт сообщает о таймауте и продолжает, не зависая бесконечно
### Requirement: Только-добавление и сухой прогон
Скрипт SHALL работать в режиме только-добавления: SHALL не удалять и не изменять
существующие данные. Повторные запуски SHALL добавлять новые данные, различимые по метке
прогона. При сухом прогоне скрипт SHALL сообщать план и SHALL не выполнять изменяющих
запросов.
#### Scenario: Повторный запуск
- **WHEN** скрипт запускается повторно
- **THEN** существующие данные сохраняются, а новые добавляются с новой меткой прогона
#### Scenario: Сухой прогон
- **WHEN** включён режим сухого прогона
- **THEN** скрипт не отправляет запросов, изменяющих данные
### Requirement: Минимум зависимостей
Основной функционал скрипта SHALL работать на стандартной библиотеке Python, включая
HTTP-клиент; Pillow SHALL быть необязательной зависимостью.
#### Scenario: Запуск без внешних пакетов
- **WHEN** в окружении нет сторонних пакетов
- **THEN** скрипт выполняет базовое наполнение с изображениями PNG без EXIF
### Requirement: Запуск через Make-цель
Проект SHALL предоставлять Make-цель `testdata`, которая запускает скрипт наполнения и
передаёт ему числовые и булевы параметры. Цель SHALL не дублировать логику скрипта.
#### Scenario: Передача параметров
- **WHEN** цель запущена с заданными параметрами (число альбомов и фото, seed, булевы флаги)
- **THEN** соответствующие значения и флаги передаются в скрипт наполнения
#### Scenario: Значения по умолчанию
- **WHEN** цель запущена без параметров
- **THEN** используются документированные значения по умолчанию