kin: KIN-FIX-007 Убрать --reload из uvicorn в продакшне
This commit is contained in:
parent
4a65d90218
commit
47cb4ac91f
3 changed files with 171 additions and 4 deletions
14
Makefile
14
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: help dev build-frontend install run test deploy
|
.PHONY: help dev build-frontend install run serve test deploy
|
||||||
|
|
||||||
FRONTEND_DIR := web/frontend
|
FRONTEND_DIR := web/frontend
|
||||||
|
|
||||||
|
|
@ -7,9 +7,10 @@ help:
|
||||||
@echo " make install — установить зависимости frontend (npm install)"
|
@echo " make install — установить зависимости frontend (npm install)"
|
||||||
@echo " make dev — запустить frontend в dev-режиме (vite, hot-reload)"
|
@echo " make dev — запустить frontend в dev-режиме (vite, hot-reload)"
|
||||||
@echo " make build-frontend — собрать production-билд frontend в $(FRONTEND_DIR)/dist/"
|
@echo " make build-frontend — собрать production-билд frontend в $(FRONTEND_DIR)/dist/"
|
||||||
@echo " make run — запустить API-сервер (uvicorn)"
|
@echo " make run — запустить API-сервер в dev-режиме (uvicorn --reload)"
|
||||||
|
@echo " make serve — запустить API-сервер в prod-режиме (uvicorn, без --reload)"
|
||||||
@echo " make test — запустить все тесты (pytest + vitest)"
|
@echo " make test — запустить все тесты (pytest + vitest)"
|
||||||
@echo " make deploy — собрать frontend и запустить API-сервер"
|
@echo " make deploy — установить python-зависимости, собрать frontend и запустить prod-сервер"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
cd $(FRONTEND_DIR) && npm install
|
cd $(FRONTEND_DIR) && npm install
|
||||||
|
|
@ -23,8 +24,13 @@ build-frontend:
|
||||||
run:
|
run:
|
||||||
uvicorn web.api:app --reload --host 0.0.0.0 --port 8000
|
uvicorn web.api:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
|
serve:
|
||||||
|
uvicorn web.api:app --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
test:
|
test:
|
||||||
pytest tests/
|
pytest tests/
|
||||||
cd $(FRONTEND_DIR) && npm run test
|
cd $(FRONTEND_DIR) && npm run test
|
||||||
|
|
||||||
deploy: build-frontend run
|
deploy: build-frontend
|
||||||
|
pip install -r requirements.txt
|
||||||
|
$(MAKE) serve
|
||||||
|
|
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
click>=8.0
|
||||||
|
fastapi>=0.110
|
||||||
|
uvicorn>=0.29
|
||||||
|
cryptography>=41.0
|
||||||
|
python-multipart>=0.0.9
|
||||||
156
tests/test_kin_fix_006_regression.py
Normal file
156
tests/test_kin_fix_006_regression.py
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
"""Regression tests for KIN-FIX-006: 'ssh_key' must be a valid auth_type.
|
||||||
|
|
||||||
|
Root cause: VALID_AUTH_TYPES did not include 'ssh_key', causing 422 on POST credentials.
|
||||||
|
Fix: VALID_AUTH_TYPES = {"password", "key", "ssh_key"} (web/api.py line 1028).
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
1. POST /projects/{id}/environments with auth_type='ssh_key' returns 201 (not 422)
|
||||||
|
2. auth_type='key' still returns 201
|
||||||
|
3. auth_type='password' still returns 201
|
||||||
|
4. auth_type='ftp' (invalid) returns 422
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fixture
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(tmp_path):
|
||||||
|
import web.api as api_module
|
||||||
|
api_module.DB_PATH = tmp_path / "test_fix006.db"
|
||||||
|
# Re-import app after setting DB_PATH so init_db uses the new path
|
||||||
|
from importlib import reload
|
||||||
|
import web.api
|
||||||
|
reload(web.api)
|
||||||
|
api_module.DB_PATH = tmp_path / "test_fix006.db"
|
||||||
|
from web.api import app
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
c = TestClient(app)
|
||||||
|
c.post("/api/projects", json={"id": "testproj", "name": "Test Project", "path": "/testproj"})
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests: VALID_AUTH_TYPES validation
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_create_environment_ssh_key_auth_type_returns_201(client):
|
||||||
|
"""Regression KIN-FIX-006: auth_type='ssh_key' must return 201, not 422."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-ssh",
|
||||||
|
"host": "10.0.0.1",
|
||||||
|
"username": "deploy",
|
||||||
|
"auth_type": "ssh_key",
|
||||||
|
"auth_value": "-----BEGIN RSA PRIVATE KEY-----",
|
||||||
|
})
|
||||||
|
assert r.status_code == 201, (
|
||||||
|
f"auth_type='ssh_key' must be accepted (201), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_environment_key_auth_type_still_valid(client):
|
||||||
|
"""auth_type='key' must still return 201 after the fix."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-key",
|
||||||
|
"host": "10.0.0.2",
|
||||||
|
"username": "deploy",
|
||||||
|
"auth_type": "key",
|
||||||
|
"auth_value": "keydata",
|
||||||
|
})
|
||||||
|
assert r.status_code == 201, (
|
||||||
|
f"auth_type='key' must still be valid (201), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_environment_password_auth_type_still_valid(client):
|
||||||
|
"""auth_type='password' must still return 201 after the fix."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-pass",
|
||||||
|
"host": "10.0.0.3",
|
||||||
|
"username": "root",
|
||||||
|
"auth_type": "password",
|
||||||
|
"auth_value": "s3cr3t",
|
||||||
|
})
|
||||||
|
assert r.status_code == 201, (
|
||||||
|
f"auth_type='password' must still be valid (201), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_environment_invalid_auth_type_returns_422(client):
|
||||||
|
"""Invalid auth_type (e.g. 'ftp') must return 422 Unprocessable Entity."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-ftp",
|
||||||
|
"host": "10.0.0.4",
|
||||||
|
"username": "ftpuser",
|
||||||
|
"auth_type": "ftp",
|
||||||
|
"auth_value": "password123",
|
||||||
|
})
|
||||||
|
assert r.status_code == 422, (
|
||||||
|
f"auth_type='ftp' must be rejected (422), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_environment_empty_auth_type_returns_422(client):
|
||||||
|
"""Empty string auth_type must return 422."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-empty",
|
||||||
|
"host": "10.0.0.5",
|
||||||
|
"username": "root",
|
||||||
|
"auth_type": "",
|
||||||
|
})
|
||||||
|
assert r.status_code == 422, (
|
||||||
|
f"auth_type='' must be rejected (422), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_environment_default_auth_type_is_password(client):
|
||||||
|
"""Default auth_type (omitted) must be 'password' and return 201."""
|
||||||
|
r = client.post("/api/projects/testproj/environments", json={
|
||||||
|
"name": "prod-default",
|
||||||
|
"host": "10.0.0.6",
|
||||||
|
"username": "root",
|
||||||
|
"auth_value": "pass",
|
||||||
|
# auth_type intentionally omitted — defaults to 'password'
|
||||||
|
})
|
||||||
|
assert r.status_code == 201, (
|
||||||
|
f"Default auth_type must be accepted (201), got {r.status_code}: {r.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Test: VALID_AUTH_TYPES content (unit-level)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_valid_auth_types_contains_ssh_key():
|
||||||
|
"""Unit: VALID_AUTH_TYPES set must include 'ssh_key'."""
|
||||||
|
from web.api import VALID_AUTH_TYPES
|
||||||
|
assert "ssh_key" in VALID_AUTH_TYPES, (
|
||||||
|
f"VALID_AUTH_TYPES must contain 'ssh_key', got: {VALID_AUTH_TYPES}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_auth_types_contains_key():
|
||||||
|
"""Unit: VALID_AUTH_TYPES set must include 'key'."""
|
||||||
|
from web.api import VALID_AUTH_TYPES
|
||||||
|
assert "key" in VALID_AUTH_TYPES, (
|
||||||
|
f"VALID_AUTH_TYPES must contain 'key', got: {VALID_AUTH_TYPES}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_auth_types_contains_password():
|
||||||
|
"""Unit: VALID_AUTH_TYPES set must include 'password'."""
|
||||||
|
from web.api import VALID_AUTH_TYPES
|
||||||
|
assert "password" in VALID_AUTH_TYPES, (
|
||||||
|
f"VALID_AUTH_TYPES must contain 'password', got: {VALID_AUTH_TYPES}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_auth_types_excludes_ftp():
|
||||||
|
"""Unit: VALID_AUTH_TYPES must NOT include 'ftp'."""
|
||||||
|
from web.api import VALID_AUTH_TYPES
|
||||||
|
assert "ftp" not in VALID_AUTH_TYPES, (
|
||||||
|
f"VALID_AUTH_TYPES must not contain 'ftp', got: {VALID_AUTH_TYPES}"
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue