File size: 6,283 Bytes
2a87ede
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""

Tests for dense_evolution/cli.py -- the `dense-evolution` console script.



_cmd_serve() and _cmd_offline_composer()'s own side effects (starting a

real server, hitting the real internet) are mocked out at the call

boundary so these tests stay fast and offline; the dispatch logic in

main() and _cmd_offline_composer()'s own HTML-parsing/path logic are

exercised for real.

"""
import builtins
import io
import urllib.request
from urllib.error import URLError

import pytest

from dense_evolution import cli


def test_main_with_no_args_prints_usage_to_stdout_and_exits_0(capsys):
    with pytest.raises(SystemExit) as exc_info:
        cli.main([])
    assert exc_info.value.code == 0
    out = capsys.readouterr()
    assert "usage: dense-evolution" in out.out
    assert out.err == ""


def test_main_with_unknown_command_prints_usage_to_stderr_and_exits_1(capsys):
    with pytest.raises(SystemExit) as exc_info:
        cli.main(["not-a-real-command"])
    assert exc_info.value.code == 1
    out = capsys.readouterr()
    assert "usage: dense-evolution" in out.err
    assert out.out == ""


def test_main_serve_dispatches_to_cmd_serve(monkeypatch):
    called = []
    monkeypatch.setattr(cli, "_cmd_serve", lambda: called.append(True))
    cli.main(["serve"])
    assert called == [True]


def test_main_offline_composer_uses_default_dest(monkeypatch):
    seen = []
    monkeypatch.setattr(cli, "_cmd_offline_composer", lambda dest: seen.append(dest))
    cli.main(["offline-composer"])
    assert seen == ["composer-offline"]


def test_main_offline_composer_uses_given_dest(monkeypatch):
    seen = []
    monkeypatch.setattr(cli, "_cmd_offline_composer", lambda dest: seen.append(dest))
    cli.main(["offline-composer", "my-folder"])
    assert seen == ["my-folder"]


def test_cmd_serve_calls_local_site_server_main(monkeypatch):
    pytest.importorskip("fastapi")  # local_site.app.server imports it at module level
    monkeypatch.setattr(cli, "_require_composer_extra", lambda: None)
    called = []
    import local_site.app.server as server_module
    monkeypatch.setattr(server_module, "main", lambda: called.append(True))
    cli._cmd_serve()
    assert called == [True]


def test_require_composer_extra_passes_when_deps_are_installed():
    pytest.importorskip("fastapi")
    pytest.importorskip("uvicorn")
    pytest.importorskip("pydantic")
    # fastapi/uvicorn/pydantic are the `composer` extra -- only meaningful
    # to assert this succeeds where they're actually installed.
    cli._require_composer_extra()


def test_require_composer_extra_exits_when_a_dep_is_missing(monkeypatch, capsys):
    real_import = builtins.__import__

    def fake_import(name, *args, **kwargs):
        if name == "fastapi":
            raise ImportError(name)
        return real_import(name, *args, **kwargs)

    monkeypatch.setattr(builtins, "__import__", fake_import)
    with pytest.raises(SystemExit) as exc_info:
        cli._require_composer_extra()
    assert exc_info.value.code == 1
    assert "pip install dense-evolution[composer]" in capsys.readouterr().err


_FAKE_PAGE_HTML = """<!doctype html><html><head>

<link rel="stylesheet" href="../assets/stylesheets/main.css">

<link rel="icon" href="../assets/favicon.svg">

<link rel="canonical" href="https://tatopenn-cell.github.io/Dense-Evolution/composer/">

<link rel="prev" href="../getting-started/">

<script src="../assets/javascripts/bundle.js"></script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>

</head><body>

<img src="../assets/logo.png">

</body></html>"""


class _FakeResponse:
    def __init__(self, data: bytes):
        self._data = data

    def read(self):
        return self._data

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        return False


def test_cmd_offline_composer_mirrors_the_page_and_same_origin_assets(tmp_path, monkeypatch):
    requested = []

    def fake_urlopen(url, timeout=30):
        requested.append(url)
        if url == cli.COMPOSER_PAGE_URL:
            return _FakeResponse(_FAKE_PAGE_HTML.encode("utf-8"))
        return _FakeResponse(b"fake-asset-bytes")

    monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)

    dest = str(tmp_path / "offline")
    page_local_path = cli._cmd_offline_composer(dest)

    # The page itself is saved at the same relative depth it has on the
    # real site (composer/index.html), not at dest/index.html directly.
    assert page_local_path.replace("\\", "/").endswith("offline/composer/index.html")
    with open(page_local_path, encoding="utf-8") as f:
        assert f.read() == _FAKE_PAGE_HTML

    # stylesheet/icon/script/img same-origin assets ARE mirrored.
    assert (tmp_path / "offline" / "assets" / "stylesheets" / "main.css").exists()
    assert (tmp_path / "offline" / "assets" / "favicon.svg").exists()
    assert (tmp_path / "offline" / "assets" / "javascripts" / "bundle.js").exists()
    assert (tmp_path / "offline" / "assets" / "logo.png").exists()

    # canonical/prev <link> tags are not assets and must never be fetched.
    assert "https://tatopenn-cell.github.io/Dense-Evolution/getting-started/" not in requested

    # a genuinely external CDN script is a different origin -- must not be downloaded.
    assert not (tmp_path / "offline" / "tex-mml-chtml.js").exists()
    assert "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" not in requested


def test_cmd_offline_composer_skips_an_asset_that_fails_to_download(tmp_path, monkeypatch, capsys):
    def fake_urlopen(url, timeout=30):
        if url == cli.COMPOSER_PAGE_URL:
            return _FakeResponse(_FAKE_PAGE_HTML.encode("utf-8"))
        raise URLError("boom")

    monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)

    dest = str(tmp_path / "offline")
    page_local_path = cli._cmd_offline_composer(dest)

    # A failed asset download must not abort the whole mirror -- the page
    # itself is still saved.
    assert io.open(page_local_path, encoding="utf-8").read() == _FAKE_PAGE_HTML
    assert "saltato (non essenziale)" in capsys.readouterr().out