Skip to content

Commit 5ecdfc9

Browse files
committed
Tests: Improve test cases to verify universal source access
1 parent 2f79fc9 commit 5ecdfc9

File tree

10 files changed

+190
-39
lines changed

10 files changed

+190
-39
lines changed

hubspot_tech_writing/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from hubspot_tech_writing.hubspot_api import HubSpotAdapter, HubSpotBlogPost, HubSpotFile
1515
from hubspot_tech_writing.util.common import ContentTypeResolver
1616
from hubspot_tech_writing.util.html import HTMLImageTranslator
17-
from hubspot_tech_writing.util.io import path_from_url, to_io
17+
from hubspot_tech_writing.util.io import open_url, to_io
1818

1919
logger = logging.getLogger(__name__)
2020

@@ -77,7 +77,7 @@ def upload(
7777
):
7878
source_path: Path
7979
if isinstance(source, str):
80-
source_path = path_from_url(source)
80+
source_path = open_url(source)
8181
else:
8282
source_path = source
8383
logger.info(f"Source: {source_path}")

hubspot_tech_writing/util/common.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import colorlog
55
from colorlog.escape_codes import escape_codes
66
from pathlibfs import Path
7-
from yarl import URL
7+
8+
from hubspot_tech_writing.util.io import path_without_scheme
89

910

1011
def setup_logging(level=logging.INFO, verbose: bool = False):
@@ -25,10 +26,7 @@ class ContentTypeResolver:
2526
TEXT_SUFFIXES = MARKUP_SUFFIXES + HTML_SUFFIXES + [".txt"]
2627

2728
def __init__(self, filepath: t.Union[str, Path]):
28-
self.url = URL(str(filepath))
29-
if self.url.is_absolute():
30-
self.url = self.url.with_scheme("")
31-
self.path = Path(str(self.url))
29+
self.path = path_without_scheme(filepath)
3230
self.suffix = self.path.suffix
3331

3432
def is_markup(self):
@@ -42,8 +40,3 @@ def is_text(self):
4240

4341
def is_file(self):
4442
return not self.is_text()
45-
46-
47-
def url_to_path(filepath: str):
48-
url = URL(str(filepath)).with_scheme("")
49-
return Path(str(url))

hubspot_tech_writing/util/html.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,8 @@ def upload(self) -> "HTMLImageTranslator":
8888
return self
8989
for image_local in self.images_local:
9090
hs_file = self.uploader(source=image_local.src, name=image_local.src.name)
91-
image_url = hs_file.url
9291
image_remote: HTMLImage = deepcopy(image_local)
93-
image_remote.src = image_url
92+
image_remote.src = hs_file.url
9493
self.images_remote.append(image_remote)
9594
return self
9695

hubspot_tech_writing/util/io.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@
99

1010
@contextlib.contextmanager
1111
def to_io(source: t.Union[str, Path, t.IO]) -> t.Generator[t.IO, None, None]:
12+
"""
13+
Main context manager for accessing resources.
14+
Before accessing / opening, it converges a path string, object, or IO handle, to an IO handle.
15+
"""
1216
fp: t.IO
1317
if isinstance(source, io.TextIOWrapper):
1418
fp = source
1519
elif isinstance(source, (str, Path, PathPlus)):
1620
source = str(source)
17-
path = path_from_url(source)
21+
path = open_url(source)
1822
fp = path.open(mode="rt")
19-
"""
20-
if source.startswith("http://") or source.startswith("https://"):
21-
response = requests.get(source, timeout=10.0)
22-
fp = io.StringIO(response.text)
23-
else:
24-
fp = open(source, "r")
25-
"""
2623
else:
2724
raise TypeError(f"Unable to converge to IO handle. type={type(source)}, value={source}")
2825
yield fp
2926
fp.close()
3027

3128

32-
def path_from_url(url: str) -> PathPlus:
29+
def open_url(url: str) -> PathPlus:
3330
"""
34-
Convert GitHub HTTP URL to pathlibfs / fsspec URL.
31+
Access URL, with specific handling for GitHub URLs.
32+
33+
When approached using a GitHub HTTP URL, converge it to a pathlibfs / fsspec URL,
34+
and open it.
3535
3636
Input URLs
3737
----------
@@ -54,7 +54,7 @@ def path_from_url(url: str) -> PathPlus:
5454
}
5555

5656
real_path_fragments = path_fragments[2:]
57-
if path_fragments[2] == "blob":
57+
if path_fragments[2] in ["blob", "raw"]:
5858
real_path_fragments = path_fragments[4:]
5959

6060
downstream_url = "github://" + "/".join(real_path_fragments)
@@ -63,3 +63,13 @@ def path_from_url(url: str) -> PathPlus:
6363
else:
6464
path = PathPlus(url)
6565
return path
66+
67+
68+
def path_without_scheme(url_like: str) -> PathPlus:
69+
"""
70+
Return a pathlibfs Path, without the scheme.
71+
"""
72+
url = URL(str(url_like))
73+
if url.is_absolute():
74+
url = url.with_scheme("")
75+
return PathPlus(str(url))

tests/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,30 @@ def markdownfile() -> Path:
1515
return Path(__file__).parent / "data" / "hubspot-blog-post-original.md"
1616

1717

18+
def get_markdownurl(infix: str = "", scheme: str = "https:") -> str:
19+
return f"{scheme}//github.com/crate-workbench/hubspot-tech-writing/{infix}tests/data/hubspot-blog-post-original.md"
20+
21+
22+
@pytest.fixture
23+
def markdownurl_https_raw() -> str:
24+
return get_markdownurl(infix="raw/main/")
25+
26+
27+
@pytest.fixture
28+
def markdownurl_github_https_bare() -> str:
29+
return get_markdownurl(scheme="github+https:")
30+
31+
32+
@pytest.fixture
33+
def markdownurl_github_https_raw() -> str:
34+
return get_markdownurl(infix="raw/main/", scheme="github+https:")
35+
36+
37+
@pytest.fixture
38+
def markdownurl_github_https_blob() -> str:
39+
return get_markdownurl(infix="blob/main/", scheme="github+https:")
40+
41+
1842
@pytest.fixture
1943
def markdownfile_minimal_broken_links() -> Path:
2044
return Path(__file__).parent / "data" / "minimal-broken-links.md"

tests/test_core.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ def test_convert_file(markdownfile):
1313
check_content(html)
1414

1515

16-
def test_convert_url():
17-
url = "https://github.com/crate-workbench/hubspot-tech-writing/raw/main/tests/data/hubspot-blog-post-original.md"
18-
html = convert(url)
16+
def test_convert_url(markdownurl_https_raw):
17+
html = convert(markdownurl_https_raw)
1918
check_content(html)
2019

2120

tests/test_hubspot_blogpost.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import json
21
import os
32

43
import pytest
5-
from hubspot.cms.blogs.blog_posts.rest import RESTResponse
6-
from urllib3 import HTTPResponse
74

85
from hubspot_tech_writing.core import delete_blogpost, upload
6+
from tests.test_hubspot_file import response_simulator_upload
7+
from tests.util import mkresponse
98

109

11-
def mkresponse(data, status=200, reason="OK"):
12-
body = json.dumps(data).encode("utf-8")
13-
return RESTResponse(HTTPResponse(body=body, status=status, reason=reason))
10+
def response_simulator_create(self, method, url, **kwargs):
11+
if method == "GET" and url == "https://api.hubapi.com/cms/v3/blogs/posts":
12+
response = mkresponse({"total": 0, "results": []})
13+
elif method == "POST" and url == "https://api.hubapi.com/cms/v3/blogs/posts":
14+
response = mkresponse({"id": "12345"}, status=201, reason="Created")
15+
elif method == "PATCH" and url == "https://api.hubapi.com/cms/v3/blogs/posts/12345":
16+
response = mkresponse({"id": "12345"})
17+
else:
18+
raise ValueError(f"No HTTP conversation mock for: method={method}, url={url}")
19+
return response
1420

1521

16-
def response_simulator_create(self, method, url, **kwargs):
22+
def response_simulator_create_with_image(self, method, url, **kwargs):
1723
if method == "GET" and url == "https://api.hubapi.com/cms/v3/blogs/posts":
1824
response = mkresponse({"total": 0, "results": []})
1925
elif method == "POST" and url == "https://api.hubapi.com/cms/v3/blogs/posts":
@@ -93,7 +99,7 @@ def test_upload_blogpost_create_from_markdown(hubspot_access_token, mocker, capl
9399

94100

95101
def test_upload_blogpost_update(hubspot_access_token, mocker, caplog, tmp_path):
96-
tmpfile = tmp_path / "foo.html"
102+
tmpfile = tmp_path / "foo.md"
97103
tmpfile.write_text("# Foobar\nFranz jagt im komplett verwahrlosten Taxi quer durch Bayern.")
98104

99105
mocker.patch("hubspot.cms.blogs.blog_posts.rest.RESTClientObject.request", response_simulator_update)
@@ -109,6 +115,35 @@ def test_upload_blogpost_update(hubspot_access_token, mocker, caplog, tmp_path):
109115
assert "Saving blog post: HubSpotBlogPost identifier=12345, name=hstw-test" in caplog.text
110116

111117

118+
def test_upload_blogpost_with_image(hubspot_access_token, mocker, caplog, tmp_path):
119+
mdfile = tmp_path / "foo.md"
120+
pngfile = tmp_path / "images" / "bar.png"
121+
pngfile.parent.mkdir()
122+
mdfile.write_text("![bar](images/bar.png)")
123+
pngfile.write_bytes(b"")
124+
125+
mocker.patch("hubspot.cms.blogs.blog_posts.rest.RESTClientObject.request", response_simulator_create_with_image)
126+
mocker.patch("hubspot.files.files.rest.RESTClientObject.request", response_simulator_upload)
127+
upload(
128+
source=mdfile,
129+
name="hstw-test",
130+
content_group_id="55844199082",
131+
folder_path="/path/to/assets",
132+
access_token=hubspot_access_token,
133+
)
134+
135+
assert "Uploading file:" in caplog.text
136+
137+
assert "Loading file: HubSpotFile identifier=None, name=bar.png, folder=/path/to/assets" in caplog.text
138+
assert "Searching for 'bar.png' in folder path '/path/to/assets'" in caplog.text
139+
assert "File does not exist: bar.png" in caplog.text
140+
assert "Creating: HubSpotFile identifier=None, name=bar.png, folder=/path/to/assets" in caplog.text
141+
assert "Saving file: HubSpotFile identifier=12345, name=bar.png, folder=/path/to/assets" in caplog.text
142+
143+
assert "Loading blog post: HubSpotBlogPost identifier=None, name=hstw-test" in caplog.text
144+
assert "Saving blog post: HubSpotBlogPost identifier=12345, name=hstw-test" in caplog.text
145+
146+
112147
def test_delete_by_identifier(hubspot_access_token, mocker, caplog):
113148
mocker.patch.dict(os.environ, {"CONFIRM": "yes"})
114149
mocker.patch("hubspot.cms.blogs.blog_posts.rest.RESTClientObject.request", response_simulator_delete_id)

tests/test_hubspot_file.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55

66
from hubspot_tech_writing.core import delete_file, upload
77

8-
from .test_hubspot_blogpost import mkresponse
8+
from .util import mkresponse
99

1010

1111
def response_simulator_upload(self, method, url, **kwargs):
1212
if method == "GET" and url == "https://api.hubapi.com/files/v3/files/search":
1313
response = mkresponse({"total": 0, "results": []})
1414
elif method == "POST" and url == "https://api.hubapi.com/files/v3/files":
15-
response = mkresponse({"id": "12345"}, status=201, reason="Created")
15+
response = mkresponse(
16+
{"id": "12345", "url": "https://site.example/hubfs/any.png"}, status=201, reason="Created"
17+
)
1618
elif method == "PUT" and url == "https://api.hubapi.com/files/v3/files/12345":
17-
response = mkresponse({"id": "12345"})
19+
response = mkresponse({"id": "12345", "url": "https://site.example/hubfs/any.png"})
1820
else:
1921
raise ValueError(f"No HTTP conversation mock for: method={method}, url={url}")
2022
return response

tests/test_util.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from hubspot_tech_writing.util.common import ContentTypeResolver
2+
from hubspot_tech_writing.util.io import open_url, path_without_scheme
3+
4+
5+
def test_content_type_resolver_local_html():
6+
ctr = ContentTypeResolver("/path/to/document.html")
7+
assert ctr.is_file() is False
8+
assert ctr.is_text() is True
9+
assert ctr.is_markup() is False
10+
assert ctr.is_html() is True
11+
assert ctr.suffix == ".html"
12+
13+
14+
def test_content_type_resolver_local_markdown():
15+
ctr = ContentTypeResolver("/path/to/document.md")
16+
assert ctr.is_file() is False
17+
assert ctr.is_text() is True
18+
assert ctr.is_markup() is True
19+
assert ctr.is_html() is False
20+
assert ctr.suffix == ".md"
21+
22+
23+
def test_content_type_resolver_local_text():
24+
ctr = ContentTypeResolver("/path/to/document.txt")
25+
assert ctr.is_file() is False
26+
assert ctr.is_text() is True
27+
assert ctr.is_markup() is False
28+
assert ctr.is_html() is False
29+
assert ctr.suffix == ".txt"
30+
31+
32+
def test_content_type_resolver_local_image():
33+
ctr = ContentTypeResolver("/path/to/document.png")
34+
assert ctr.is_file() is True
35+
assert ctr.is_text() is False
36+
assert ctr.is_markup() is False
37+
assert ctr.is_html() is False
38+
assert ctr.suffix == ".png"
39+
40+
41+
def test_content_type_resolver_remote_https():
42+
ctr = ContentTypeResolver("https://site.example/path/to/document.md")
43+
assert ctr.is_file() is False
44+
assert ctr.is_text() is True
45+
assert ctr.is_markup() is True
46+
assert ctr.is_html() is False
47+
assert ctr.suffix == ".md"
48+
49+
50+
def test_content_type_resolver_remote_github():
51+
ctr = ContentTypeResolver("github://site.example/path/to/document.md")
52+
assert ctr.is_file() is False
53+
assert ctr.is_text() is True
54+
assert ctr.is_markup() is True
55+
assert ctr.is_html() is False
56+
assert ctr.suffix == ".md"
57+
58+
59+
def test_content_type_resolver_remote_github_https():
60+
ctr = ContentTypeResolver("github+https://site.example/path/to/document.md")
61+
assert ctr.is_file() is False
62+
assert ctr.is_text() is True
63+
assert ctr.is_markup() is True
64+
assert ctr.is_html() is False
65+
assert ctr.suffix == ".md"
66+
67+
68+
def test_path_without_scheme_local():
69+
assert str(path_without_scheme("/path/to/document.md")) == "/path/to/document.md"
70+
71+
72+
def test_path_without_scheme_url():
73+
assert str(path_without_scheme("https://site.example/path/to/document.md")) == "//site.example/path/to/document.md"
74+
75+
76+
def test_path_from_url(markdownurl_github_https_bare, markdownurl_github_https_raw, markdownurl_github_https_blob):
77+
reference = "github://tests/data/hubspot-blog-post-original.md"
78+
assert str(open_url(markdownurl_github_https_bare)) == reference
79+
assert str(open_url(markdownurl_github_https_raw)) == reference
80+
assert str(open_url(markdownurl_github_https_blob)) == reference

tests/util.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import json
2+
3+
from hubspot.cms.blogs.blog_posts.rest import RESTResponse
4+
from urllib3 import HTTPResponse
5+
6+
7+
def mkresponse(data, status=200, reason="OK"):
8+
body = json.dumps(data).encode("utf-8")
9+
return RESTResponse(HTTPResponse(body=body, status=status, reason=reason))

0 commit comments

Comments
 (0)