File size: 5,791 Bytes
69e310f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""The Hugging Face Space deploy configuration.

A deploy variable is code that only ever executes on the platform, so a mistake
in one is invisible until the Space refuses to boot — three minutes after the
push, with a message that names the field but not the file. Everything checkable
without credentials is checked here instead.
"""

from __future__ import annotations

import importlib.util
from pathlib import Path
from types import ModuleType

import pytest

from app.core.settings import Settings

REPO_ROOT = Path(__file__).resolve().parents[3]


def _load_deploy_script() -> ModuleType:
    """Import the script by path: `scripts/` is a directory of CLIs, not a package."""
    path = REPO_ROOT / "scripts" / "deploy_space.py"
    spec = importlib.util.spec_from_file_location("deploy_space", path)
    assert spec is not None and spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


deploy = _load_deploy_script()


class TestSpaceVariables:
    def test_they_build_a_real_settings_object(self) -> None:
        """Every deployed variable must be one `Settings` actually accepts.

        `ENVIRONMENT: prod` instead of `production` is the archetype: pydantic
        rejects it at import, so the container dies before serving a request and
        the platform reports it as a crashed build.
        """
        settings = Settings(**{key.lower(): value for key, value in deploy.SPACE_VARIABLES.items()})

        assert settings.environment == "production"
        assert settings.mcp_transport == "stdio"

    def test_production_is_declared(self) -> None:
        """On `local` the API mints an approval token into a file for the console.

        A Space shares no filesystem with the console, so that handshake cannot
        work there — and trusting a file would let anything able to write to the
        image supply the credential. `production` disables it and requires an
        explicit APPROVAL_TOKEN.
        """
        assert deploy.SPACE_VARIABLES["ENVIRONMENT"] == "production"

    def test_cors_is_never_a_wildcard(self) -> None:
        """These endpoints take a bearer token; an open origin list undoes that."""
        origins = deploy.SPACE_VARIABLES["CORS_ALLOW_ORIGINS"]
        assert "*" not in origins
        assert origins.startswith("https://")

    def test_no_secret_is_committed_as_a_variable(self) -> None:
        """Space variables are public. The day one holds a password, so is the password."""
        secretish = ("KEY", "TOKEN", "PASSWORD", "SECRET", "DATABASE_URL")
        leaked = [name for name in deploy.SPACE_VARIABLES if any(s in name for s in secretish)]
        assert leaked == []


class TestFrontmatter:
    def test_the_committed_config_passes_its_own_check(self) -> None:
        config = (REPO_ROOT / ".hf-space.yml").read_text(encoding="utf-8")
        assert deploy.frontmatter_problems(config) == []

    def test_a_missing_sdk_is_caught(self) -> None:
        problems = deploy.frontmatter_problems("title: AlphaBrief\napp_port: 7860\n")
        assert any("sdk: docker" in problem for problem in problems)

    def test_a_wrong_port_is_caught(self) -> None:
        """Spaces routes to the declared port; a mismatch is a silent 504."""
        problems = deploy.frontmatter_problems("sdk: docker\napp_port: 8000\n")
        assert any("app_port" in problem for problem in problems)

    def test_an_over_long_description_is_caught_before_the_upload(self) -> None:
        """The Hub enforces this in a pre-receive hook — after the whole push."""
        long = "x" * (deploy.SHORT_DESCRIPTION_MAX + 1)
        problems = deploy.frontmatter_problems(
            f'sdk: docker\napp_port: 7860\nshort_description: "{long}"\n'
        )
        assert any("short_description" in problem for problem in problems)

    def test_a_description_containing_a_colon_is_measured_whole(self) -> None:
        """Splitting on every colon would truncate at the first and under-measure."""
        problems = deploy.frontmatter_problems(
            'sdk: docker\napp_port: 7860\nshort_description: "a: b"\n'
        )
        assert problems == []


class TestPreflight:
    def test_the_repository_as_committed_is_deployable(self) -> None:
        assert deploy.preflight() == []

    def test_the_dockerfile_builds_from_the_repository_root(self) -> None:
        """Spaces builds ./Dockerfile with the repo root as context, not apps/api.

        A COPY of a path relative to apps/api resolves against the root and fails
        with a bare "file not found" naming a path that does exist.
        """
        content = (REPO_ROOT / "apps" / "api" / "Dockerfile").read_text(encoding="utf-8")
        assert "COPY apps/api/" in content

    def test_the_generated_root_dockerfile_is_gitignored(self) -> None:
        """It is written onto the deploy commit only; on main it would be a duplicate."""
        ignored = (REPO_ROOT / ".gitignore").read_text(encoding="utf-8")
        assert "/Dockerfile" in ignored


class TestPushFailureAdvice:
    @pytest.mark.parametrize(
        ("stderr", "expected"),
        [
            ("remote: YAML metadata is invalid", ".hf-space.yml"),
            ("remote: refusing binary files", "Xet"),
            ("fatal: Authentication failed", "WRITE token"),
        ],
    )
    def test_the_remote_reason_selects_the_advice(self, stderr: str, expected: str) -> None:
        """Guessing sends you to the tokens page while the real fault is elsewhere."""
        assert expected in deploy.explain_push_failure(stderr)

    def test_an_unrecognised_failure_does_not_invent_a_cause(self) -> None:
        advice = deploy.explain_push_failure("remote: something entirely new")
        assert "above" in advice