|
18 | 18 |
|
19 | 19 | from taskbadger import Action, EmailIntegration, StatusEnum |
20 | 20 | from taskbadger.celery import Task, task_publish_handler |
21 | | -from taskbadger.mug import Badger |
| 21 | +from taskbadger.mug import Badger, Settings |
| 22 | +from taskbadger.systems.celery import CelerySystemIntegration |
22 | 23 | from tests.utils import task_for_test |
23 | 24 |
|
24 | 25 |
|
@@ -297,6 +298,85 @@ def test_celery_publish_handler_task_not_registered_locally(): |
297 | 298 | assert headers["taskbadger_task_id"] == create.return_value.id |
298 | 299 |
|
299 | 300 |
|
| 301 | +@pytest.mark.parametrize(("track_header", "expect_created"), [({}, True), ({"taskbadger_track": False}, False)]) |
| 302 | +def test_celery_publish_handler_opt_out_beats_auto_track(track_header, expect_created): |
| 303 | + """An explicit `taskbadger_track=False` header opts a single execution out, even |
| 304 | + when auto-tracking would otherwise pick the task up.""" |
| 305 | + settings = Settings("https://taskbadger.net", "token", "org", "proj", systems={"celery": CelerySystemIntegration()}) |
| 306 | + Badger.current.bind(settings) |
| 307 | + try: |
| 308 | + with mock.patch("taskbadger.celery.create_task_safe") as create: |
| 309 | + create.return_value = task_for_test() |
| 310 | + headers = {"id": "abc123", "task": "auto.tracked.task", **track_header} |
| 311 | + task_publish_handler(sender="auto.tracked.task", headers=headers, body=[[], {}, {}]) |
| 312 | + finally: |
| 313 | + Badger.current.bind(None) |
| 314 | + |
| 315 | + assert create.called is expect_created |
| 316 | + |
| 317 | + |
| 318 | +@pytest.mark.usefixtures("_bind_settings") |
| 319 | +def test_celery_track_attr_not_passed_to_create(celery_session_app): |
| 320 | + """`taskbadger_track` is a tracking switch, not a task field, so it must never reach |
| 321 | + `create_task` — which would raise on the unexpected kwarg and lose the task.""" |
| 322 | + |
| 323 | + @celery_session_app.task(base=Task, name="track.attr.task", taskbadger_track=False) |
| 324 | + def track_attr_task(): |
| 325 | + return 1 |
| 326 | + |
| 327 | + with mock.patch("taskbadger.celery.create_task_safe") as create: |
| 328 | + create.return_value = task_for_test() |
| 329 | + headers = {"id": "abc123", "task": "track.attr.task", "taskbadger_track": True} |
| 330 | + task_publish_handler(sender="track.attr.task", headers=headers, body=[[], {}, {}]) |
| 331 | + |
| 332 | + assert "track" not in create.call_args.kwargs |
| 333 | + |
| 334 | + |
| 335 | +@pytest.mark.usefixtures("_bind_settings") |
| 336 | +def test_celery_task_opt_out(celery_session_app, celery_session_worker): |
| 337 | + """`headers={"taskbadger_track": False}` prevents tracking of a single execution.""" |
| 338 | + |
| 339 | + @celery_session_app.task(bind=True, base=Task) |
| 340 | + def add_opt_out(self, a, b): |
| 341 | + assert self.taskbadger_task_id is None, "task should not be tracked" |
| 342 | + return a + b |
| 343 | + |
| 344 | + celery_session_worker.reload() |
| 345 | + |
| 346 | + with ( |
| 347 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 348 | + mock.patch("taskbadger.celery.update_task_safe") as update, |
| 349 | + ): |
| 350 | + result = add_opt_out.apply_async((2, 2), headers={"taskbadger_track": False}) |
| 351 | + assert result.get(timeout=10, propagate=True) == 4 |
| 352 | + |
| 353 | + create.assert_not_called() |
| 354 | + update.assert_not_called() |
| 355 | + |
| 356 | + |
| 357 | +@pytest.mark.usefixtures("_bind_settings") |
| 358 | +def test_celery_task_opt_out_kwarg(celery_session_app, celery_session_worker): |
| 359 | + """`taskbadger_track=False` also works as a `taskbadger_`-prefixed option, the way the |
| 360 | + other per-call options are passed, and never leaks into the create_task kwargs.""" |
| 361 | + |
| 362 | + @celery_session_app.task(bind=True, base=Task) |
| 363 | + def add_opt_out_kwarg(self, a, b): |
| 364 | + assert self.taskbadger_task_id is None, "task should not be tracked" |
| 365 | + return a + b |
| 366 | + |
| 367 | + celery_session_worker.reload() |
| 368 | + |
| 369 | + with ( |
| 370 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 371 | + mock.patch("taskbadger.celery.update_task_safe") as update, |
| 372 | + ): |
| 373 | + result = add_opt_out_kwarg.apply_async((2, 2), taskbadger_track=False) |
| 374 | + assert result.get(timeout=10, propagate=True) == 4 |
| 375 | + |
| 376 | + create.assert_not_called() |
| 377 | + update.assert_not_called() |
| 378 | + |
| 379 | + |
300 | 380 | @pytest.mark.usefixtures("_bind_settings") |
301 | 381 | def test_celery_task_custom_queue(celery_session_app, celery_session_worker): |
302 | 382 | @celery_session_app.task(bind=True, base=Task) |
@@ -542,6 +622,30 @@ def task_map_fn(self, a): |
542 | 622 | assert Badger.current.session().client is None |
543 | 623 |
|
544 | 624 |
|
| 625 | +@pytest.mark.usefixtures("_bind_settings") |
| 626 | +def test_task_map_opt_out(celery_session_worker): |
| 627 | + """Canvas tasks honour the opt-out too. They are created in the worker rather than |
| 628 | + at publish time, so the header is checked there.""" |
| 629 | + |
| 630 | + @celery.shared_task(bind=True, base=Task) |
| 631 | + def task_map_opt_out_fn(self, a): |
| 632 | + return a * 2 |
| 633 | + |
| 634 | + celery_session_worker.reload() |
| 635 | + |
| 636 | + map_canvas = task_map_opt_out_fn.map(list(range(3))) |
| 637 | + |
| 638 | + with ( |
| 639 | + mock.patch("taskbadger.celery.create_task_safe") as create, |
| 640 | + mock.patch("taskbadger.celery.update_task_safe") as update, |
| 641 | + ): |
| 642 | + result = map_canvas.apply_async(headers={"taskbadger_track": False}) |
| 643 | + assert result.get(timeout=10, propagate=True) == [0, 2, 4] |
| 644 | + |
| 645 | + create.assert_not_called() |
| 646 | + update.assert_not_called() |
| 647 | + |
| 648 | + |
545 | 649 | @pytest.mark.usefixtures("_bind_settings") |
546 | 650 | def test_task_starmap(celery_session_worker): |
547 | 651 | """Tasks executed via starmap canvas primitive should be tracked.""" |
|
0 commit comments