This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/main by this push:
new 9ddd7f3 Update tests and fix a bug with sending email to start a vote
9ddd7f3 is described below
commit 9ddd7f3f15f24f801d33d921e9888dcef8f9ded4
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Dec 5 09:57:45 2025 +0000
Update tests and fix a bug with sending email to start a vote
---
atr/construct.py | 2 +-
atr/db/interaction.py | 24 +++++-------------------
atr/storage/writers/vote.py | 22 +++++++++++++++++++++-
atr/tasks/vote.py | 29 +++++++++++------------------
atr/util.py | 15 +++++++++++++++
playwright/test.py | 20 ++++++++++----------
6 files changed, 63 insertions(+), 49 deletions(-)
diff --git a/atr/construct.py b/atr/construct.py
index 027b3d8..70b3043 100644
--- a/atr/construct.py
+++ b/atr/construct.py
@@ -161,7 +161,7 @@ async def start_vote_body(body: str, options:
StartVoteOptions) -> str:
import atr.get.vote as vote
async with db.session() as data:
- # Do not limit by phase, as it may be at RELEASE_CANDIDATE here if
called by the task
+ # Do not limit by phase, as it may be at RELEASE_CANDIDATE already
release = await data.release(
project_name=options.project_name,
version=options.version_name,
diff --git a/atr/db/interaction.py b/atr/db/interaction.py
index e59aebb..d1b210f 100644
--- a/atr/db/interaction.py
+++ b/atr/db/interaction.py
@@ -19,7 +19,7 @@ import contextlib
import datetime
import enum
from collections.abc import AsyncGenerator, Sequence
-from typing import Any, Final
+from typing import Any
import packaging.version as version
import sqlalchemy
@@ -36,20 +36,6 @@ import atr.user as user
import atr.util as util
import atr.web as web
-# TEST_MID: Final[str | None] =
"CAH5JyZo8QnWmg9CwRSwWY=givhxw4nilyenjo71fkdk81j5...@mail.gmail.com"
-TEST_MID: Final[str | None] = None
-_THREAD_URLS_FOR_DEVELOPMENT: Final[dict[str, str]] = {
- "CAH5JyZo8QnWmg9CwRSwWY=givhxw4nilyenjo71fkdk81j5...@mail.gmail.com":
"https://lists.apache.org/thread/z0o7xnjnyw2o886rxvvq2ql4rdfn754w",
- "[email protected]":
"https://lists.apache.org/thread/619hn4x796mh3hkk3kxg1xnl48dy2s64",
- "CAA9ykM+bMPNk=bof9hj0o+mjn1igppoj+pkdzhcam0ddvi+...@mail.gmail.com":
"https://lists.apache.org/thread/x0m3p2xqjvflgtkb6oxqysm36cr9l5mg",
- "CAFHDsVzgtfboqYF+a3owaNf+55MUiENWd3g53mU4rD=whkx...@mail.gmail.com":
"https://lists.apache.org/thread/brj0k3g8pq63g8f7xhmfg2rbt1240nts",
- "camomwmrvktqk7k2-otztreo0jjxzo2g5ynw3gsoks_pxwpz...@mail.gmail.com":
"https://lists.apache.org/thread/y5rqp5qk6dmo08wlc3g20n862hznc9m8",
- "CANVKqzfLYj6TAVP_Sfsy5vFbreyhKskpRY-vs=f7aled+rl...@mail.gmail.com":
"https://lists.apache.org/thread/oy969lhh6wlzd51ovckn8fly9rvpopwh",
- "cah4123zwgtkwszheu7qnmbyla-yvykz2w+djh_uchpmuzaa...@mail.gmail.com":
"https://lists.apache.org/thread/7111mqyc25sfqxm6bf4ynwhs0bk0r4ys",
- "CADL1oArKFcXvNb1MJfjN=10-yrfkxgpltrurdmm1r7ygatk...@mail.gmail.com":
"https://lists.apache.org/thread/d7119h2qm7jrd5zsbp8ghkk0lpvnnxnw",
- "[email protected]":
"https://lists.apache.org/thread/gzjd2jv7yod5sk5rgdf4x33g5l3fdf5o",
-}
-
class ApacheUserMissingError(RuntimeError):
def __init__(self, message: str, fingerprint: str | None, primary_uid: str
| None) -> None:
@@ -338,11 +324,11 @@ async def releases_in_progress(project: sql.Project) ->
list[sql.Release]:
def task_mid_get(latest_vote_task: sql.Task) -> str | None:
- if util.is_dev_environment():
- import atr.db.interaction as interaction
+ # if util.is_dev_environment():
+ # import atr.db.interaction as interaction
- return interaction.TEST_MID
- # TODO: Improve this
+ # return interaction.TEST_MID
+ # # TODO: Improve this
result = latest_vote_task.result
if not isinstance(result, results.VoteInitiate):
diff --git a/atr/storage/writers/vote.py b/atr/storage/writers/vote.py
index 6dae337..eb41204 100644
--- a/atr/storage/writers/vote.py
+++ b/atr/storage/writers/vote.py
@@ -18,6 +18,7 @@
# Removing this will cause circular imports
from __future__ import annotations
+import datetime
from typing import Literal
import atr.construct as construct
@@ -168,6 +169,25 @@ class CommitteeParticipant(FoundationCommitter):
# Presumably this sets the default, and the form takes precedence?
# ReleasePolicy.min_hours can also be 0, though
+ # Calculate vote end time for template substitution
+ vote_start = datetime.datetime.now(datetime.UTC)
+ vote_end = vote_start + datetime.timedelta(hours=vote_duration_choice)
+ vote_end_str = vote_end.strftime("%Y-%m-%d %H:%M:%S UTC")
+
+ # Perform template substitutions in the body before passing to task
+ # This must be done here and not in the task because we need
util.as_url
+ body_substituted = await construct.start_vote_body(
+ body_data,
+ construct.StartVoteOptions(
+ asfuid=asf_uid,
+ fullname=asf_fullname,
+ project_name=project_name,
+ version_name=version_name,
+ vote_duration=vote_duration_choice,
+ vote_end=vote_end_str,
+ ),
+ )
+
# Create a task for vote initiation
task = sql.Task(
status=sql.TaskStatus.QUEUED,
@@ -179,7 +199,7 @@ class CommitteeParticipant(FoundationCommitter):
initiator_id=asf_uid,
initiator_fullname=asf_fullname,
subject=subject_data,
- body=body_data,
+ body=body_substituted,
).model_dump(),
asf_uid=asf_uid,
project_name=project_name,
diff --git a/atr/tasks/vote.py b/atr/tasks/vote.py
index 0097671..f53906d 100644
--- a/atr/tasks/vote.py
+++ b/atr/tasks/vote.py
@@ -17,7 +17,6 @@
import datetime
-import atr.construct as construct
import atr.db as db
import atr.db.interaction as interaction
import atr.log as log
@@ -101,21 +100,9 @@ async def _initiate_core_logic(args: Initiate) ->
results.Results | None:
log.error(error_msg)
raise VoteInitiationError(error_msg)
- # Construct email
+ # The body has already been substituted by the route handler
subject = args.subject
-
- # Perform substitutions in the body
- body = await construct.start_vote_body(
- args.body,
- construct.StartVoteOptions(
- asfuid=args.initiator_id,
- fullname=args.initiator_fullname,
- project_name=release.project.name,
- version_name=release.version,
- vote_duration=args.vote_duration,
- vote_end=vote_end_str,
- ),
- )
+ body = args.body
permitted_recipients = util.permitted_voting_recipients(args.initiator_id,
release.committee.name)
if args.email_to not in permitted_recipients:
@@ -131,8 +118,14 @@ async def _initiate_core_logic(args: Initiate) ->
results.Results | None:
body=body,
)
- # Send the email
- mid, mail_errors = await mail.send(message)
+ if util.is_dev_environment():
+ # Pretend to send the mail
+ log.info("Dev environment detected, pretending to send mail")
+ mid = util.DEV_TEST_MID
+ mail_errors = []
+ else:
+ # Send the mail
+ mid, mail_errors = await mail.send(message)
# Original success message structure
result = results.VoteInitiate(
@@ -146,7 +139,7 @@ async def _initiate_core_logic(args: Initiate) ->
results.Results | None:
)
if mail_errors:
- log.warning(f"Start vote for {args.release_name}: sending to
{args.email_to} gave errors: {mail_errors}")
+ log.warning(f"Start vote for {args.release_name}: sending to
{args.email_to} gave errors: {mail_errors}")
else:
log.info(f"Vote email sent successfully to {args.email_to}")
return result
diff --git a/atr/util.py b/atr/util.py
index 09e38ef..0745002 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -57,6 +57,18 @@ import atr.user as user
T = TypeVar("T")
USER_TESTS_ADDRESS: Final[str] = "[email protected]"
+DEV_TEST_MID: Final[str] =
"CAH5JyZo8QnWmg9CwRSwWY=givhxw4nilyenjo71fkdk81j5...@mail.gmail.com"
+DEV_THREAD_URLS: Final[dict[str, str]] = {
+ "CAH5JyZo8QnWmg9CwRSwWY=givhxw4nilyenjo71fkdk81j5...@mail.gmail.com":
"https://lists.apache.org/thread/z0o7xnjnyw2o886rxvvq2ql4rdfn754w",
+ "[email protected]":
"https://lists.apache.org/thread/619hn4x796mh3hkk3kxg1xnl48dy2s64",
+ "CAA9ykM+bMPNk=bof9hj0o+mjn1igppoj+pkdzhcam0ddvi+...@mail.gmail.com":
"https://lists.apache.org/thread/x0m3p2xqjvflgtkb6oxqysm36cr9l5mg",
+ "CAFHDsVzgtfboqYF+a3owaNf+55MUiENWd3g53mU4rD=whkx...@mail.gmail.com":
"https://lists.apache.org/thread/brj0k3g8pq63g8f7xhmfg2rbt1240nts",
+ "camomwmrvktqk7k2-otztreo0jjxzo2g5ynw3gsoks_pxwpz...@mail.gmail.com":
"https://lists.apache.org/thread/y5rqp5qk6dmo08wlc3g20n862hznc9m8",
+ "CANVKqzfLYj6TAVP_Sfsy5vFbreyhKskpRY-vs=f7aled+rl...@mail.gmail.com":
"https://lists.apache.org/thread/oy969lhh6wlzd51ovckn8fly9rvpopwh",
+ "cah4123zwgtkwszheu7qnmbyla-yvykz2w+djh_uchpmuzaa...@mail.gmail.com":
"https://lists.apache.org/thread/7111mqyc25sfqxm6bf4ynwhs0bk0r4ys",
+ "CADL1oArKFcXvNb1MJfjN=10-yrfkxgpltrurdmm1r7ygatk...@mail.gmail.com":
"https://lists.apache.org/thread/d7119h2qm7jrd5zsbp8ghkk0lpvnnxnw",
+ "[email protected]":
"https://lists.apache.org/thread/gzjd2jv7yod5sk5rgdf4x33g5l3fdf5o",
+}
class SshFingerprintError(ValueError):
@@ -834,6 +846,9 @@ async def task_archive_url(task_mid: str, recipient: str |
None = None) -> str |
if "@" not in task_mid:
return None
+ if is_dev_environment() and (task_mid in DEV_THREAD_URLS):
+ return DEV_THREAD_URLS[task_mid]
+
recipient_address = recipient or USER_TESTS_ADDRESS
lid = recipient_address.replace("@", ".")
url =
f"https://lists.apache.org/api/email.json?id=%3C{task_mid}%3E&listid=%3C{lid}%3E"
diff --git a/playwright/test.py b/playwright/test.py
index df51509..dc8b12f 100755
--- a/playwright/test.py
+++ b/playwright/test.py
@@ -206,21 +206,21 @@ def lifecycle_05_resolve_vote(page: sync_api.Page,
credentials: Credentials, ver
logging.info("Vote page loaded successfully")
# Wait until the vote initiation background task has completed
- # When it finishes the page shows a banner that begins with "Vote thread
started"
- # We poll for that banner before moving on
+ # When it finishes the page shows a "view thread" link
+ # We poll for that link before moving on
# Otherwise the subsequent Resolve step cannot find the completed
VOTE_INITIATE task
- # TODO: Make a poll_for_tasks_completion style function that can be used
here
- banner_locator = page.locator("p.text-success:has-text('Vote thread
started')")
- banner_found = False
+ # Note that this actually doesn't matter in dev testing
+ thread_link_locator = page.locator('a:has-text("view thread")')
+ link_found = False
for _ in range(30):
- if banner_locator.is_visible(timeout=500):
- banner_found = True
- logging.info("Vote initiation banner detected, task completed")
+ if thread_link_locator.is_visible(timeout=500):
+ link_found = True
+ logging.info("Vote thread link detected, task completed")
break
time.sleep(0.5)
page.reload()
- if not banner_found:
- logging.warning("Vote initiation banner not detected after 15s,
proceeding anyway")
+ if not link_found:
+ logging.warning("Vote thread link not detected after 15s, proceeding
anyway")
logging.info("Locating the 'Resolve vote' button")
tabulate_form_locator =
page.locator(f'form[action="/resolve/{TEST_PROJECT}/{version_name}"]')
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]