fix: normalize HTML responses for deterministic Django replay #58
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Fixes non-deterministic HTML class attribute ordering that caused replay comparison failures in Django apps (e.g., django-unfold admin). Also fixes a critical bug in protobuf Value conversion that caused empty mock data during replay.
Context
The HTML class ordering issue was discovered during Django replay where templates (likely from django-unfold) rendered CSS classes in non-deterministic order. The protobuf bug was causing mock database rows to be empty during replay, leading to Django's
StopIterationerror when populating models.Changes
HTML Normalization for Django Replay
html_utils.pywithnormalize_html_class_ordering()to sort CSS classes alphabetically withinclass="..."attributeshtml_utils.py(deleted separatecsrf_utils.py)normalize_html_body()andnormalize_html_response()helpers to reduce code duplicationmiddleware.pyto use consolidated helpers for both RECORD and REPLAY modesProtobuf Utils Refactor
value_to_python()andstruct_to_dict()into newprotobuf_utils.pyhasattr()was incorrectly used to check protobuf oneof fields (always returns True for protobuf attributes, causing all values to return None)WhichOneof), and betterproto Value (is_set)communicator.pyandtypes.py