Skip to content

Commit b6ce359

Browse files
dragid10kjaymiller
andauthored
Add tests for Conferences script (#404)
* Add tests for parsing Conference Issues * Refactor top-level logic into testable functions * Run pre-commit linters * Only add to conference list if the conference details could actually be parse * Move gh token env var to function We don't always need to use the GITHUB_TOKEN during tests, so let's make it an explicit call if we need it * Add sleep command to playwright tests This should allow playwright to finish setting up before running tests * Add delay between each test to make CI tests more consistent * Linter fixes * Reduce startup time so tests don't take as long to run * Reduce delay between tests to 1 second * Update _conferences/__main__.py Co-authored-by: Jay Miller <[email protected]> * Fix linter issues --------- Co-authored-by: Jay Miller <[email protected]>
1 parent 9e90858 commit b6ce359

File tree

3 files changed

+234
-69
lines changed

3 files changed

+234
-69
lines changed

.github/workflows/playwright.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,13 @@ jobs:
3131
bundler-cache: true
3232
- name: Jekyll detached and pytest
3333
run: |
34+
# Start up local copy of site
3435
bundle exec jekyll serve --detach
36+
37+
# Sleep for 5 secs to allow Jekyll to start
38+
startup_wait=5
39+
echo "Sleeping for $startup_wait seconds"
40+
sleep $startup_wait
41+
42+
# Run tests
3543
python -m pytest

_conferences/__main__.py

Lines changed: 105 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,76 +6,112 @@
66

77
import yaml
88
from github import Auth, Github
9+
from github.Issue import Issue
10+
from github.PaginatedList import PaginatedList
911

10-
TOKEN = os.getenv("GITHUB_TOKEN", "")
1112
ROOT = Path(__file__).parent.parent
1213
conferences_path = ROOT / "_data/conferences.yml"
1314

14-
auth = Auth.Token(TOKEN)
15-
g = Github(auth=auth)
16-
17-
repo = g.get_repo("BlackPythonDevs/blackpythondevs.github.io")
18-
open_issues = repo.get_issues(state="open", labels=["conference"])
19-
conferences = []
20-
today = datetime.combine(datetime.now(), time())
21-
22-
for issue in open_issues:
23-
if "conference" in [label.name for label in issue.labels]:
24-
# Extract fields from issue body
25-
name_match = re.search(
26-
r"Conference Name(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue.body
27-
)
28-
url_match = re.search(r"URL(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue.body)
29-
dates_match = re.search(
30-
r"Conference Dates(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue.body
31-
)
32-
type_match = re.search(
33-
r"Conference Type(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue.body
34-
)
35-
location_match = re.search(
36-
r"Conference Location(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue.body
37-
)
38-
summary_match = re.search(
39-
r"Summary(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}",
40-
issue.body,
41-
re.DOTALL,
42-
)
43-
speaking_match = re.search(
44-
r"Speaking(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}### Code of Conduct(?:\r\n|\n){2}",
45-
issue.body,
46-
re.DOTALL,
47-
)
48-
49-
# Set a default value of None for when the url field isn't as expected
50-
valid_url = None
51-
52-
# Ensure the url field is not blank and the url matches the regex
53-
if url_match is not None and url_match[1].strip() != "":
54-
# Parse the url and see if a scheme (`https`) is included in it
55-
# If not, then prepend `https` to the url from the issue body
56-
# This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/
57-
parsed_url = urlparse(url_match[1])
58-
if "http" not in parsed_url.scheme.casefold():
59-
valid_url = f"https://{url_match[1]}"
60-
61-
if dates_match:
62-
conferenceDates = dates_match[1]
63-
# Parse the end date of the conference
64-
endDateStr = conferenceDates.split("-")[1].strip()
65-
endDate = datetime.strptime(endDateStr, "%d %b %Y")
66-
# Check if the conference end date is greater than today
67-
if endDate >= today:
68-
conference = {
69-
"name": name_match[1],
70-
"url": valid_url,
71-
"dates": dates_match[1],
72-
"type": type_match[1],
73-
"location": location_match[1],
74-
"summary": summary_match[1],
75-
"speaking": speaking_match[1] if speaking_match else "",
76-
}
77-
conferences.append(conference)
78-
79-
# Write the conferences to the _data/conferences.yml file
80-
with conferences_path.open("w") as f:
81-
yaml.dump(conferences, f)
15+
16+
def create_github_client():
17+
gh_token = os.getenv("GITHUB_TOKEN", "")
18+
auth = Auth.Token(gh_token)
19+
client = Github(auth=auth)
20+
return client
21+
22+
23+
def get_open_issues(gh: Github) -> PaginatedList[Issue]:
24+
repo = gh.get_repo("BlackPythonDevs/blackpythondevs.github.io")
25+
issues = repo.get_issues(state="open", labels=["conference"])
26+
return issues
27+
28+
29+
def parse_conference_details(issue_body: str) -> dict | None:
30+
# Extract fields from issue body
31+
name_match = re.search(
32+
r"Conference Name(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body
33+
)
34+
url_match = re.search(r"URL(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body)
35+
dates_match = re.search(
36+
r"Conference Dates(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body
37+
)
38+
type_match = re.search(
39+
r"Conference Type(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body
40+
)
41+
location_match = re.search(
42+
r"Conference Location(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}", issue_body
43+
)
44+
summary_match = re.search(
45+
r"Summary(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}",
46+
issue_body,
47+
re.DOTALL,
48+
)
49+
speaking_match = re.search(
50+
r"Speaking(?:\r\n|\n){2}(.*?)(?:\r\n|\n){2}### Code of Conduct(?:\r\n|\n){2}",
51+
issue_body,
52+
re.DOTALL,
53+
)
54+
55+
# Set a default value of None for when the url field isn't as expected
56+
valid_url = normalize_url() if not url_match else normalize_url(url_match[1])
57+
58+
if dates_match:
59+
conferenceDates = dates_match[1]
60+
# Parse the end date of the conference
61+
endDateStr = conferenceDates.split("-")[1].strip()
62+
endDate = datetime.strptime(endDateStr, "%d %b %Y")
63+
# Check if the conference end date is greater than today
64+
today = datetime.combine(datetime.now(), time())
65+
66+
if endDate >= today:
67+
conference = {
68+
"name": name_match[1],
69+
"url": valid_url,
70+
"dates": dates_match[1],
71+
"type": type_match[1],
72+
"location": location_match[1],
73+
"summary": summary_match[1],
74+
"speaking": speaking_match[1] if speaking_match else "",
75+
}
76+
return conference
77+
return None
78+
79+
80+
def normalize_url(url_match: str = None):
81+
valid_url = None
82+
# Ensure the url field is not blank and the url matches the regex
83+
if url_match is not None and url_match.strip() != "":
84+
# Parse the url and see if a scheme (`https`) is included in it
85+
# If not, then prepend `https` to the url from the issue body
86+
# This guards against the website thinking the passed in url is another page on https://blackpythondevs.com/
87+
parsed_url = urlparse(url_match)
88+
if "http" not in parsed_url.scheme.casefold():
89+
valid_url = f"https://{url_match}"
90+
else:
91+
valid_url = url_match
92+
return valid_url
93+
94+
95+
def write_conferences_to_file(confs: list[dict]):
96+
# Write the conferences to the _data/conferences.yml file
97+
with conferences_path.open("w") as f:
98+
yaml.dump(confs, f)
99+
100+
101+
if __name__ == "__main__":
102+
conferences = []
103+
104+
# Create Github client object
105+
gh_client = create_github_client()
106+
107+
# Get open issues from repo
108+
open_issues: PaginatedList[Issue] = get_open_issues(gh_client)
109+
110+
# Parse each conference issue so long as it has the "conference" label
111+
for issue in open_issues:
112+
if "conference" in [label.name for label in issue.labels]:
113+
parsed_conf = parse_conference_details(issue_body=issue.body)
114+
if parsed_conf:
115+
conferences.append(parsed_conf)
116+
117+
write_conferences_to_file(conferences)

tests/test.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import time
2+
13
import pytest
24
from playwright.sync_api import Page, expect
35

6+
from _conferences.__main__ import parse_conference_details
7+
48
live_server_url = "http://127.0.0.1:4000"
59

610
routes = [
@@ -11,6 +15,13 @@
1115
]
1216

1317

18+
# Add a delay to each test to help with playwright race conditions
19+
@pytest.fixture(autouse=True)
20+
def slow_down_tests():
21+
yield
22+
time.sleep(1)
23+
24+
1425
@pytest.mark.parametrize("url", routes)
1526
def test_destination(
1627
page: Page,
@@ -101,3 +112,113 @@ def test_mailto_bpdevs(page: Page) -> None:
101112
page.goto(f"{live_server_url}")
102113
mailto = page.get_by_role("link", name="email")
103114
expect(mailto).to_have_attribute("href", "mailto:[email protected]")
115+
116+
117+
def test_conference_parsing_valid_url():
118+
example_conf_issue = """### Conference Name
119+
120+
Test Conference Title
121+
122+
### URL
123+
124+
https://microsoft.com
125+
126+
### Conference Dates
127+
128+
10 - 15 Sep 2050
129+
130+
### Conference Type
131+
132+
both
133+
134+
### Conference Location
135+
136+
Redmond, WA, USA
137+
138+
### Summary
139+
140+
Test Conference Summary
141+
142+
### Speaking
143+
144+
* [Satya Nadella](https://www.linkedin.com/in/satyanadella/)
145+
"""
146+
expected_name = "Test Conference Title"
147+
expected_url = "https://microsoft.com"
148+
parsed_conf = parse_conference_details(issue_body=example_conf_issue)
149+
150+
assert parsed_conf["name"] == expected_name
151+
assert parsed_conf["url"] == expected_url
152+
153+
154+
def test_conference_parsing_logic_no_url_scheme():
155+
example_conf_issue = """### Conference Name
156+
157+
Test Conference Title
158+
159+
### URL
160+
161+
microsoft.com
162+
163+
### Conference Dates
164+
165+
10 - 15 Sep 2050
166+
167+
### Conference Type
168+
169+
both
170+
171+
### Conference Location
172+
173+
Redmond, WA, USA
174+
175+
### Summary
176+
177+
Test Conference Summary
178+
179+
### Speaking
180+
181+
* [Satya Nadella](https://www.linkedin.com/in/satyanadella/)
182+
"""
183+
expected_name = "Test Conference Title"
184+
expected_url = "https://microsoft.com"
185+
parsed_conf = parse_conference_details(issue_body=example_conf_issue)
186+
187+
assert parsed_conf["name"] == expected_name
188+
assert parsed_conf["url"] == expected_url
189+
190+
191+
def test_conference_parsing_logic_no_url():
192+
example_conf_issue = """### Conference Name
193+
194+
Test Conference Title
195+
196+
### URL
197+
198+
199+
### Conference Dates
200+
201+
10 - 15 Sep 2050
202+
203+
### Conference Type
204+
205+
both
206+
207+
### Conference Location
208+
209+
Redmond, WA, USA
210+
211+
### Summary
212+
213+
Test Conference Summary
214+
215+
### Speaking
216+
217+
* [Satya Nadella](https://www.linkedin.com/in/satyanadella/)
218+
"""
219+
expected_name = "Test Conference Title"
220+
expected_url = None
221+
parsed_conf = parse_conference_details(issue_body=example_conf_issue)
222+
223+
assert parsed_conf["name"] == expected_name
224+
assert parsed_conf["url"] == expected_url

0 commit comments

Comments
 (0)