## MODIFIED Requirements ### Requirement: Раздача веб-приложения Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/jira_hub/web/app/`, читая файлы с диска на каждый запрос: - `GET /app/assets/<путь>` — файл из `assets/` сборки; путь, который после разрешения выходит за `assets/`, не существует или содержит нулевой байт, — 404; `Content-Type` по расширению (`.js` — `text/javascript; charset=utf-8`, `.css` — `text/css; charset=utf-8`, `.svg` — `image/svg+xml`, `.png` — `image/png`, `.ico` — `image/x-icon`, `.woff2` — `font/woff2`, `.map` — `application/json`, прочее — `application/octet-stream`); `Cache-Control: public, max-age=31536000, immutable`; - любой другой `GET /app` или `GET /app/…` — `index.html` сборки, `text/html; charset=utf-8`, `Cache-Control: no-cache`; - если `index.html` сборки нет — на любой `GET /app…` 503 `text/html; charset=utf-8` с текстом, что приложение не собрано и нужно выполнить `make web` в каталоге jira-hub; - `GET /` и `GET /links` — 302 с `Location: /app/board`. Сборка приложения не хранится в git; её создаёт `make web`. #### Scenario: Файл сборки - **WHEN** в сборке есть `assets/index-abc123.js`, запрошен `GET /app/assets/index-abc123.js` - **THEN** 200, `Content-Type: text/javascript; charset=utf-8`, `Cache-Control` с `immutable`, тело — содержимое файла #### Scenario: Путь приложения - **WHEN** запрошен `GET /app/issues/AURORA-1620` - **THEN** 200, тело — `index.html` сборки, `Cache-Control: no-cache` #### Scenario: Выход за каталог сборки - **WHEN** запрошен `GET /app/assets/../../api.py` (в том числе в виде `%2e%2e`) - **THEN** 404, содержимое файлов вне `assets/` не отдаётся #### Scenario: Приложение не собрано - **WHEN** каталога сборки нет, запрошен `GET /app/board` - **THEN** 503, тело — HTML с подсказкой `make web` #### Scenario: Старый адрес доски - **WHEN** запрошен `GET /links` или `GET /` - **THEN** 302, `Location: /app/board` #### Scenario: Нулевой байт в пути - **WHEN** запрошен `GET /app/assets/%00` или `GET /app/assets/a%00.js` - **THEN** 404 `not_found`, а не 500