"""Unit tests for hosting_panel.validators.""" from __future__ import annotations import os os.environ.setdefault("PANEL_SECRET_KEY", "x" * 64) os.environ.setdefault("PANEL_ENCRYPTION_KEY", "90mbg1KLmFKBpoSs5nstm7Ew-p3Q-J3ya3Zbuhwbtho=") import pytest from fastapi import HTTPException from hosting_panel import validators as v # ---- slug ----------------------------------------------------------------- def test_slug_accepts_normal(): assert v.validate_slug("my-bot") == "my-bot" def test_slug_lowercases_and_strips(): assert v.validate_slug(" My-Bot ") == "my-bot" @pytest.mark.parametrize("bad", [ "", # empty "a", # too short "-lead", # leading hyphen "trail-", # trailing hyphen "has spaces", "has/slash", "has\\slash", "x" * 200, # too long None, ]) def test_slug_rejects_invalid(bad): with pytest.raises(HTTPException): v.validate_slug(bad) # ---- bot token (Telegram format: :) ---------------------- @pytest.mark.parametrize("ok", [ "1234567890:AAH_bot_token_secret_value_long_enough", ]) def test_bot_token_accepts(ok): assert v.validate_bot_token(ok) == ok @pytest.mark.parametrize("bad", [ "short", # too short "x" * 61, # too long "x" * 34, # one short "", # empty "noseparator-here-35+chars", # missing colon "1234:spaces inside token-padding-padding-padding", # has spaces -> rejected ]) def test_bot_token_rejects(bad): with pytest.raises(HTTPException): v.validate_bot_token(bad) def test_bot_token_with_colon_35_chars_works(): # bot_id:secret, total length 35 tok = "1234:" + "A" * 30 # 5 + 30 = 35 assert v.validate_bot_token(tok) == tok # ---- hf token (hf_... length 30-60) -------------------------------------- def test_hf_token_30_chars_works(): tok = "hf_" + "x" * 27 # 3 + 27 = 30 assert v.validate_hf_token(tok) == tok def test_hf_token_60_chars_works(): tok = "hf_" + "x" * 57 assert v.validate_hf_token(tok) == tok def test_hf_token_rejects_short(): with pytest.raises(HTTPException): v.validate_hf_token("hf_" + "x" * 5) # 8 chars def test_hf_token_rejects_long(): with pytest.raises(HTTPException): v.validate_hf_token("hf_" + "x" * 60) # ---- proxy token ---------------------------------------------------------- def test_proxy_token_accepts(): assert v.validate_proxy_token("proxy-token-long-enough-1234567890") == "proxy-token-long-enough-1234567890" # ---- source repo ---------------------------------------------------------- def test_source_repo_hf_format(): assert v.validate_source_repo("owner/name") == "owner/name" assert v.validate_source_repo("owner-name/repo-name") == "owner-name/repo-name" def test_source_repo_hf_rejects_no_slash(): with pytest.raises(HTTPException): v.validate_source_repo("noslash") def test_source_repo_github_format(): assert v.validate_source_repo( "https://github.com/owner/repo.git", source_type="github", ) == "https://github.com/owner/repo.git" def test_source_repo_github_rejects_bare_name(): with pytest.raises(HTTPException): v.validate_source_repo("owner/repo", source_type="github") # ---- source type ---------------------------------------------------------- def test_source_type_accepts_hf_default(): # Empty -> "hf" assert v.validate_source_type("") == "hf" assert v.validate_source_type("hf") == "hf" assert v.validate_source_type("github") == "github" @pytest.mark.parametrize("bad", ["ftp", "http", "spaces", "BOGUS"]) def test_source_type_rejects(bad): with pytest.raises(HTTPException): v.validate_source_type(bad) # ---- framework ------------------------------------------------------------ def test_framework_default_is_docker(): assert v.validate_framework("") == "docker" def test_framework_accepts(): assert v.validate_framework("python") == "python" assert v.validate_framework("node") == "node" assert v.validate_framework("fastapi/aiogram") == "fastapi/aiogram" def test_framework_lowercases(): """Validator lowercases; this is intentional -- reject via special chars only.""" assert v.validate_framework("Python") == "python" def test_framework_rejects_special_chars(): with pytest.raises(HTTPException): v.validate_framework("python@3.11") # ---- branch --------------------------------------------------------------- def test_branch_default_is_main(): assert v.validate_branch("") == "main" def test_branch_accepts(): assert v.validate_branch("feature/auth") == "feature/auth" assert v.validate_branch("main") == "main" def test_branch_rejects_special_chars(): with pytest.raises(HTTPException): v.validate_branch("feature; rm -rf /") # ---- nvidia api key ------------------------------------------------------- def test_nvidia_key_accepts_60_chars(): key = "nvapi-" + "x" * 54 # 60 chars assert v.validate_nvidia_api_key(key) == key def test_nvidia_key_rejects_short(): with pytest.raises(HTTPException): v.validate_nvidia_api_key("nvapi-x") # ---- url ------------------------------------------------------------------ def test_url_optional_empty(): assert v.validate_optional_url("", "render_url") == "" def test_url_accepts_https(): assert v.validate_optional_url("https://example.com", "url") == "https://example.com" def test_url_rejects_no_scheme(): with pytest.raises(HTTPException): v.validate_optional_url("example.com", "url") # ---- model ---------------------------------------------------------------- def test_model_default(): assert v.validate_optional_nvidia_model("") == "minimaxai/minimax-m3" def test_model_accepts_custom(): assert v.validate_optional_nvidia_model("openai/gpt-4") == "openai/gpt-4" def test_model_rejects_special_chars(): with pytest.raises(HTTPException): v.validate_optional_nvidia_model("model@name")