From b6a28f2ab2d27e73bd4b0c08b70969317599fd0d Mon Sep 17 00:00:00 2001 From: carlos Date: Wed, 16 Sep 2026 17:03:02 -0500 Subject: [PATCH] fix: respect whether a sink is enabled when dumping data to ClickHouse The dump_data_to_clickhouse management command dumped whatever object it was given, ignoring EVENT_SINK_CLICKHOUSE__ENABLED, while the Celery task in tasks.py already gates on Sink.is_enabled(). This meant an instance that had opted out of PII collection could still populate the user_profile and external_id tables via a manual dump. A disabled sink now logs at info level and dumps nothing, matching the behaviour of the task. --- .../commands/dump_data_to_clickhouse.py | 7 ++++ .../commands/test_dump_data_to_clickhouse.py | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/platform_plugin_aspects/management/commands/dump_data_to_clickhouse.py b/platform_plugin_aspects/management/commands/dump_data_to_clickhouse.py index ae6594bb..1cd4724d 100644 --- a/platform_plugin_aspects/management/commands/dump_data_to_clickhouse.py +++ b/platform_plugin_aspects/management/commands/dump_data_to_clickhouse.py @@ -194,6 +194,13 @@ def handle(self, *args, **options): raise CommandError(message) Sink = ModelBaseSink.get_sink_by_model_name(options["object"]) + + if not Sink.is_enabled(): + log.info( + f"Sink for {options['object']} is disabled, no data will be dumped." + ) + return + sink = Sink(connection_overrides, log) dump_target_objects_to_clickhouse( sink, diff --git a/platform_plugin_aspects/tests/commands/test_dump_data_to_clickhouse.py b/platform_plugin_aspects/tests/commands/test_dump_data_to_clickhouse.py index 98e7959d..9aef6ee9 100644 --- a/platform_plugin_aspects/tests/commands/test_dump_data_to_clickhouse.py +++ b/platform_plugin_aspects/tests/commands/test_dump_data_to_clickhouse.py @@ -4,14 +4,25 @@ from collections import namedtuple from datetime import datetime +from unittest.mock import patch import django.core.management.base import pytest from django.core.management import call_command +from django.test import override_settings from django_mock_queries.query import MockModel, MockSet from platform_plugin_aspects.sinks.base_sink import ModelBaseSink +# The dummy sink stands in for a real, enabled sink. is_enabled() needs both a +# model config entry and the enabled setting, so provide them for these tests. +dummy_sink_enabled = override_settings( + EVENT_SINK_CLICKHOUSE_MODEL_CONFIG={ + "dummy": {"module": "django.contrib.auth.models", "model": "User"}, + }, + EVENT_SINK_CLICKHOUSE_DUMMY_ENABLED=True, +) + CommandOptions = namedtuple( "TestCommandOptions", ["options", "expected_num_submitted", "expected_logs"] ) @@ -180,6 +191,7 @@ def dump_command_basic_options(): yield option +@dummy_sink_enabled @pytest.mark.parametrize("test_command_option", dump_command_basic_options()) def test_dump_courses_options(test_command_option, caplog): option_combination, expected_num_submitted, expected_outputs = test_command_option @@ -223,6 +235,7 @@ def dump_basic_invalid_options(): yield option +@dummy_sink_enabled @pytest.mark.parametrize("test_command_option", dump_basic_invalid_options()) def test_dump_courses_options_invalid(test_command_option, caplog): option_combination, expected_num_submitted, expected_outputs = test_command_option @@ -233,3 +246,22 @@ def test_dump_courses_options_invalid(test_command_option, caplog): # assert mock_dump_data.apply_async.call_count == expected_num_submitted for expected_output in expected_outputs: assert expected_output in caplog.text + + +@override_settings( + EVENT_SINK_CLICKHOUSE_MODEL_CONFIG={ + "dummy": {"module": "django.contrib.auth.models", "model": "User"}, + }, + EVENT_SINK_CLICKHOUSE_DUMMY_ENABLED=False, +) +@patch("platform_plugin_aspects.sinks.base_sink.WaffleFlag.is_enabled") +def test_dump_data_disabled_sink(mock_waffle_flag_is_enabled, caplog): + """ + A disabled sink should log and dump nothing. + """ + mock_waffle_flag_is_enabled.return_value = False + + call_command("dump_data_to_clickhouse", object="dummy") + + assert "Sink for dummy is disabled, no data will be dumped." in caplog.text + assert "Dumped" not in caplog.text