"""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}" )