|
| 1 | +import druks.contrib.software_factory.subscribers # noqa: F401 |
| 2 | +import pytest |
| 3 | +from druks.contrib.software_factory.app import SoftwareFactory |
| 4 | +from druks.contrib.software_factory.issues.enums import Status |
| 5 | +from druks.contrib.software_factory.issues.models import IssuesProject, Ticket |
| 6 | +from druks.contrib.software_factory.models import WorkItem |
| 7 | +from druks.contrib.software_factory.ticketing.enums import TicketStatus |
| 8 | +from druks.contrib.software_factory.ticketing.issues import IssuesTracker |
| 9 | +from druks.contrib.software_factory.workflows import Build |
| 10 | +from druks.core.apis.exceptions import UnknownTicketError |
| 11 | +from druks.services.models import ServiceIdentity |
| 12 | + |
| 13 | +from software_factory.factories import make_test_work_item |
| 14 | + |
| 15 | + |
| 16 | +def _pin_software_factory_settings(monkeypatch, **values): |
| 17 | + settings = SoftwareFactory.Settings(**values) |
| 18 | + |
| 19 | + async def _settings(cls): |
| 20 | + return settings |
| 21 | + |
| 22 | + monkeypatch.setattr(SoftwareFactory, "settings", classmethod(_settings)) |
| 23 | + |
| 24 | + |
| 25 | +async def _connect_github() -> None: |
| 26 | + await ServiceIdentity.connect( |
| 27 | + "github", |
| 28 | + identity={"app_id": "1", "slug": "druks-operator"}, |
| 29 | + secrets={"private_key": "operator-pem", "webhook_secret": "hook-secret"}, |
| 30 | + ) |
| 31 | + |
| 32 | + |
| 33 | +@pytest.mark.parametrize( |
| 34 | + ("asked", "board"), |
| 35 | + [ |
| 36 | + (TicketStatus.TRIGGER, Status.READY_FOR_AGENT), |
| 37 | + (TicketStatus.IN_PROGRESS, Status.IN_PROGRESS), |
| 38 | + (TicketStatus.IN_REVIEW, Status.IN_REVIEW), |
| 39 | + (TicketStatus.DONE, Status.DONE), |
| 40 | + (TicketStatus.BACKLOG, Status.BACKLOG), |
| 41 | + (TicketStatus.CANCELED, Status.CANCELLED), |
| 42 | + ], |
| 43 | +) |
| 44 | +async def test_issues_tracker_maps_ticket_status_onto_the_board(druks_db, asked, board): |
| 45 | + project = await IssuesProject.create(name="widget", prefix="WID") |
| 46 | + ticket = await Ticket.create(project_id=project.id, title="one") |
| 47 | + |
| 48 | + async with IssuesTracker() as tracker: |
| 49 | + await tracker.set_status(ticket.identifier, asked) |
| 50 | + |
| 51 | + assert (await Ticket.get_for_identifier(ticket.identifier)).status == board |
| 52 | + |
| 53 | + |
| 54 | +async def test_issues_tracker_raises_for_an_unknown_key(druks_db): |
| 55 | + with pytest.raises(UnknownTicketError, match="NOPE-1"): |
| 56 | + await IssuesTracker().set_status("NOPE-1", TicketStatus.IN_PROGRESS) |
| 57 | + |
| 58 | + |
| 59 | +@pytest.mark.parametrize( |
| 60 | + ("asked", "board"), |
| 61 | + [ |
| 62 | + (TicketStatus.IN_PROGRESS, Status.IN_PROGRESS), |
| 63 | + (TicketStatus.IN_REVIEW, Status.IN_REVIEW), |
| 64 | + (TicketStatus.DONE, Status.DONE), |
| 65 | + (TicketStatus.BACKLOG, Status.BACKLOG), |
| 66 | + ], |
| 67 | +) |
| 68 | +async def test_work_item_status_writes_through_to_the_issues_ticket( |
| 69 | + druks_db, monkeypatch, asked, board |
| 70 | +): |
| 71 | + _pin_software_factory_settings(monkeypatch, tracker="issues") |
| 72 | + project = await IssuesProject.create(name="widget", prefix="WID") |
| 73 | + ticket = await Ticket.create(project_id=project.id, title="one") |
| 74 | + item = await make_test_work_item( |
| 75 | + repo="acme/widget", source="issues", ticket_key=ticket.identifier, title="one" |
| 76 | + ) |
| 77 | + |
| 78 | + await item.set_ticket_status(asked) |
| 79 | + |
| 80 | + assert (await Ticket.get_for_identifier(ticket.identifier)).status == board |
| 81 | + |
| 82 | + |
| 83 | +async def test_ready_for_agent_opens_a_build_when_the_project_names_a_repo(druks_db, monkeypatch): |
| 84 | + await _connect_github() |
| 85 | + _pin_software_factory_settings(monkeypatch, tracker="issues") |
| 86 | + await make_test_work_item(repo="acme/widget", title="seed", ticket_key="SEED-1") |
| 87 | + project = await IssuesProject.create(name="widget", prefix="WID") |
| 88 | + ticket = await Ticket.create(project_id=project.id, title="Add an endpoint") |
| 89 | + started = [] |
| 90 | + |
| 91 | + async def fake_start(cls, **kwargs): |
| 92 | + started.append(kwargs) |
| 93 | + return "run-1" |
| 94 | + |
| 95 | + monkeypatch.setattr(Build, "start", classmethod(fake_start)) |
| 96 | + |
| 97 | + await ticket.transition(Status.READY_FOR_AGENT) |
| 98 | + |
| 99 | + item = await WorkItem.get_for_ticket_key(source="issues", ticket_key=ticket.identifier) |
| 100 | + assert item.source == "issues" |
| 101 | + assert item.ticket_key == "WID-1" |
| 102 | + assert started[0]["subject"].id == item.id |
| 103 | + |
| 104 | + |
| 105 | +async def test_ready_for_agent_skips_when_the_project_names_no_repo(druks_db, monkeypatch, caplog): |
| 106 | + _pin_software_factory_settings(monkeypatch, tracker="issues") |
| 107 | + project = await IssuesProject.create(name="no-such-repo", prefix="NSR") |
| 108 | + ticket = await Ticket.create(project_id=project.id, title="orphan") |
| 109 | + started = [] |
| 110 | + |
| 111 | + async def fake_start(cls, **kwargs): |
| 112 | + started.append(kwargs) |
| 113 | + return "run-x" |
| 114 | + |
| 115 | + monkeypatch.setattr(Build, "start", classmethod(fake_start)) |
| 116 | + |
| 117 | + with caplog.at_level("INFO"): |
| 118 | + await ticket.transition(Status.READY_FOR_AGENT) |
| 119 | + |
| 120 | + assert started == [] |
| 121 | + assert await WorkItem.get_for_ticket_key(source="issues", ticket_key=ticket.identifier) is None |
| 122 | + assert any("no routable repo" in record.getMessage() for record in caplog.records) |
0 commit comments