Skip to content

Commit 9aba91d

Browse files
authored
fix: preserve activation SHA256 for source-mapped activations on project sync (AAP-72873) (#1547)
## Summary - `_sync_rulebook()` in `imports.py` bulk-updated all non-auto-restart activations' `rulebook_rulesets_sha256` without excluding source-mapped activations, synchronizing the hashes and preventing the stale warning from firing - `_update_activation_content()` in `project.py` unconditionally set the activation hash to the rulebook hash for resume-waiting activations, with the same effect - Both fixes preserve the activation's original SHA256 when `source_mappings` is present, matching the existing guard in `_check_and_restart_activation()` (line 279) ## Root Cause The warning logic in `ActivationReadSerializer.to_representation()` (activation.py:1077-1089) correctly compares `activation.rulebook_rulesets_sha256` against `rulebook.rulesets_sha256` to detect divergence after a project sync. However, two code paths overwrote the activation's SHA256 to match the new rulebook hash *before* the serializer could detect the difference, making the warning unreachable. The auto-restart path (`_check_and_restart_activation`) already handled this correctly by skipping source-mapped activations when content changes. This fix applies the same guard to the two missed paths. ## Test plan - [x] New test: `test_sync_rulebook_preserves_sha256_for_source_mapped_activations` - [x] New test: `test_sync_rulebook_updates_sha256_for_non_source_mapped_activations` - [x] New test: `test_update_activation_content_preserves_sha256_for_source_mapped` - [x] New test: `test_update_activation_content_updates_sha256_for_non_source_mapped` - [x] All 75 existing tests in `test_projects.py` pass (no regressions) - [ ] End-to-end: create activation with source mapping, sync project with changed rulebook, verify warning appears in API response ## Related - **Jira:** [AAP-72873](https://redhat.atlassian.net/browse/AAP-72873) - **Related:** [AAP-65811](https://redhat.atlassian.net/browse/AAP-65811) — original design for SHA256 staleness detection <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Enhanced rulebook synchronization to properly manage content validation and cache state during activation updates, improving consistency of rulebook data handling across different activation configurations. * **Tests** * Added integration tests covering rulebook synchronization and activation content update behavior to ensure proper cache and content state management. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 5e0cbb3 commit 9aba91d

3 files changed

Lines changed: 169 additions & 3 deletions

File tree

src/aap_eda/services/project/imports.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,21 @@ def _sync_rulebook(
210210
# restart_on_project_update=True are handled by
211211
# _auto_restart_activations which needs to detect
212212
# the change via SHA256 comparison.
213-
models.Activation.objects.filter(
213+
base_qs = models.Activation.objects.filter(
214214
rulebook=rulebook,
215215
restart_on_project_update=False,
216-
).update(
216+
)
217+
base_qs.filter(source_mappings="").update(
217218
rulebook_rulesets=rulebook.rulesets,
218219
rulebook_rulesets_sha256=new_sha256,
219220
git_hash=git_hash,
220221
)
222+
# Source-mapped activations: preserve SHA256 for stale
223+
# warning detection (AAP-72873).
224+
base_qs.exclude(source_mappings="").update(
225+
rulebook_rulesets=rulebook.rulesets,
226+
git_hash=git_hash,
227+
)
221228

222229
def _find_rulebooks(self, repo: StrPath) -> Iterator[RulebookInfo]:
223230
rulebooks_dir = None

src/aap_eda/tasks/project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ def _update_activation_content(
364364
try:
365365
rulebook = models.Rulebook.objects.get(id=activation.rulebook_id)
366366
activation.rulebook_rulesets = rulebook.rulesets or ""
367-
activation.rulebook_rulesets_sha256 = rulebook.rulesets_sha256
367+
if not activation.source_mappings:
368+
activation.rulebook_rulesets_sha256 = rulebook.rulesets_sha256
368369
except ObjectDoesNotExist:
369370
logger.warning(
370371
f"Rulebook for activation "

tests/integration/tasks/test_projects.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,164 @@ def test_auto_restart_mixed_source_mappings_activations(
10621062
mock_restart.assert_called_once()
10631063

10641064

1065+
@pytest.mark.django_db
1066+
def test_sync_rulebook_preserves_sha256_for_source_mapped_activations(
1067+
default_organization,
1068+
):
1069+
"""_sync_rulebook preserves SHA256 for source-mapped activations."""
1070+
from aap_eda.services.project.imports import (
1071+
ProjectImportService,
1072+
RulebookInfo,
1073+
)
1074+
1075+
project = models.Project.objects.create(
1076+
name="Test Project",
1077+
url="https://github.com/example/repo",
1078+
organization=default_organization,
1079+
git_hash="old-hash",
1080+
)
1081+
rulebook = _create_test_rulebook(
1082+
project,
1083+
default_organization,
1084+
rulesets="old-content",
1085+
)
1086+
old_sha256 = get_rulebook_hash("old-content")
1087+
activation = _create_test_activation(
1088+
project,
1089+
default_organization,
1090+
rulebook,
1091+
name="source-mapped-activation",
1092+
restart_on_project_update=False,
1093+
rulebook_rulesets="old-content",
1094+
source_mappings="[{source: src1, event_stream: es1}]",
1095+
)
1096+
1097+
service = ProjectImportService()
1098+
rulebook_info = RulebookInfo(
1099+
relpath="test-rulebook",
1100+
raw_content="new-content",
1101+
content=None,
1102+
)
1103+
service._sync_rulebook(rulebook, rulebook_info, "new-hash")
1104+
1105+
activation.refresh_from_db()
1106+
assert activation.rulebook_rulesets == "new-content"
1107+
assert activation.rulebook_rulesets_sha256 == old_sha256
1108+
assert activation.git_hash == "new-hash"
1109+
1110+
1111+
@pytest.mark.django_db
1112+
def test_sync_rulebook_updates_sha256_for_non_source_mapped_activations(
1113+
default_organization,
1114+
):
1115+
"""_sync_rulebook updates SHA256 normally."""
1116+
from aap_eda.services.project.imports import (
1117+
ProjectImportService,
1118+
RulebookInfo,
1119+
)
1120+
1121+
project = models.Project.objects.create(
1122+
name="Test Project",
1123+
url="https://github.com/example/repo",
1124+
organization=default_organization,
1125+
git_hash="old-hash",
1126+
)
1127+
rulebook = _create_test_rulebook(
1128+
project,
1129+
default_organization,
1130+
rulesets="old-content",
1131+
)
1132+
activation = _create_test_activation(
1133+
project,
1134+
default_organization,
1135+
rulebook,
1136+
name="normal-activation",
1137+
restart_on_project_update=False,
1138+
rulebook_rulesets="old-content",
1139+
)
1140+
1141+
service = ProjectImportService()
1142+
rulebook_info = RulebookInfo(
1143+
relpath="test-rulebook",
1144+
raw_content="new-content",
1145+
content=None,
1146+
)
1147+
new_sha256 = get_rulebook_hash("new-content")
1148+
service._sync_rulebook(rulebook, rulebook_info, "new-hash")
1149+
1150+
activation.refresh_from_db()
1151+
assert activation.rulebook_rulesets == "new-content"
1152+
assert activation.rulebook_rulesets_sha256 == new_sha256
1153+
assert activation.git_hash == "new-hash"
1154+
1155+
1156+
@pytest.mark.django_db
1157+
def test_update_activation_content_preserves_sha256_for_source_mapped(
1158+
default_organization,
1159+
):
1160+
"""_update_activation_content preserves SHA256 for source-mapped."""
1161+
project = models.Project.objects.create(
1162+
name="Test Project",
1163+
url="https://github.com/example/repo",
1164+
organization=default_organization,
1165+
git_hash="new-hash",
1166+
)
1167+
rulebook = _create_test_rulebook(
1168+
project,
1169+
default_organization,
1170+
rulesets="new-content",
1171+
)
1172+
old_sha256 = get_rulebook_hash("old-content")
1173+
activation = _create_test_activation(
1174+
project,
1175+
default_organization,
1176+
rulebook,
1177+
name="source-mapped-activation",
1178+
rulebook_rulesets="old-content",
1179+
git_hash="old-hash",
1180+
source_mappings="[{source: src1, event_stream: es1}]",
1181+
)
1182+
1183+
_update_activation_content(activation, project)
1184+
1185+
assert activation.rulebook_rulesets == "new-content"
1186+
assert activation.rulebook_rulesets_sha256 == old_sha256
1187+
assert activation.git_hash == "new-hash"
1188+
1189+
1190+
@pytest.mark.django_db
1191+
def test_update_activation_content_updates_sha256_for_non_source_mapped(
1192+
default_organization,
1193+
):
1194+
"""_update_activation_content updates SHA256 without source mappings."""
1195+
project = models.Project.objects.create(
1196+
name="Test Project",
1197+
url="https://github.com/example/repo",
1198+
organization=default_organization,
1199+
git_hash="new-hash",
1200+
)
1201+
rulebook = _create_test_rulebook(
1202+
project,
1203+
default_organization,
1204+
rulesets="new-content",
1205+
)
1206+
new_sha256 = get_rulebook_hash("new-content")
1207+
activation = _create_test_activation(
1208+
project,
1209+
default_organization,
1210+
rulebook,
1211+
name="normal-activation",
1212+
rulebook_rulesets="old-content",
1213+
git_hash="old-hash",
1214+
)
1215+
1216+
_update_activation_content(activation, project)
1217+
1218+
assert activation.rulebook_rulesets == "new-content"
1219+
assert activation.rulebook_rulesets_sha256 == new_sha256
1220+
assert activation.git_hash == "new-hash"
1221+
1222+
10651223
@pytest.mark.django_db
10661224
@patch("aap_eda.tasks.project.restart_rulebook_process")
10671225
def test_auto_restart_skips_unchanged_content(

0 commit comments

Comments
 (0)