Skip to content

Extract accepted activity helpers#328

Open
ayskobtw-lil wants to merge 3 commits into
ramimbo:mainfrom
ayskobtw-lil:bounty-320-activity-helpers
Open

Extract accepted activity helpers#328
ayskobtw-lil wants to merge 3 commits into
ramimbo:mainfrom
ayskobtw-lil:bounty-320-activity-helpers

Conversation

@ayskobtw-lil
Copy link
Copy Markdown
Contributor

@ayskobtw-lil ayskobtw-lil commented May 25, 2026

Summary

  • Extract accepted-work activity serialization from app/main.py into focused app/activity.py helpers.
  • Keep the FastAPI route layer wired to the same public activity, account accepted-work, and account-page behavior while moving query/aggregation details behind a smaller module boundary.
  • Add a focused helper fallback regression so malformed accepted-work data keeps the account API schema stable.

Evidence

  • Confusion, missing step, stale example, or bug this addresses: app/main.py carried the accepted-work activity row shaping, search filtering, contributor aggregation, account summary, and safe fallback logic inline with route wiring. Moving that coherent activity subsystem into app/activity.py makes public account/activity behavior easier to review independently from OAuth, admin, MCP, wallet, and page routing code.
  • Wrote tests/test_activity_helpers.py first and observed it fail with ModuleNotFoundError: No module named 'app.activity' before extracting the helpers.

Test Evidence

  • python -m pytest tests/test_activity_helpers.py tests/test_activity.py tests/test_api_mcp.py -q -> 72 passed.
  • python -m ruff check app/activity.py app/main.py tests/test_activity_helpers.py -> All checks passed.
  • python -m ruff format --check app/activity.py app/main.py tests/test_activity_helpers.py -> 3 files already formatted.
  • python -m mypy app -> success.
  • python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check -> clean.

MRWK

Refs #320

No secrets, wallet private keys, private payout details, deployment values, private vulnerability details, or MRWK price claims are included.

Summary by CodeRabbit

Bug Fixes

  • Improved error handling for invalid or malformed proof data in activity feeds and work summaries—the system now gracefully skips these entries instead of failing.

Tests

  • Added test coverage for edge cases including database failures and malformed data validation.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This PR hardens proof JSON parsing in activity and accepted-work serializers by centralizing validation into a new _proof_payload() helper. Both _activity_row() and accepted_work_for_account() now use this helper to safely handle malformed or invalid proofs, returning empty results instead of raising errors.

Changes

Proof payload validation hardening

Layer / File(s) Summary
Proof payload validation and serializer integration
app/serializers.py
New _proof_payload() helper safely parses proof.public_json, validates it is an object with kind == "bounty_payment", and returns None on JSON/type errors. _activity_row() and accepted_work_for_account() refactored to use this helper instead of inline parsing, gracefully skipping invalid proofs.
Test coverage for malformed proofs and fallbacks
tests/test_serializers.py
BrokenSession stub added to force error paths. New test verifies activity serializers return schema-compliant empty defaults when database fails. Second test persists malformed Proof.public_json, asserts activity_to_dict, account_accepted_summary, and accepted_work_for_account all safely skip or return empty results.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • ramimbo/mergework#306: The accepted-work endpoint depends on accepted_work_for_account() which is hardened by this PR's _proof_payload() helper.
  • ramimbo/mergework#330: Both PRs modify activity and accepted-work serialization logic in app/serializers.py, particularly proof JSON handling in accepted_work_for_account() and activity-row builders.
  • ramimbo/mergework#301: Both PRs modify _activity_row() and accepted_work_for_account() serializers, so fields added in that PR are built within the same paths now hardened by this PR's validation helper.

Suggested reviewers

  • TateLyman
  • weilixiong
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: extracting accepted activity helpers from inline route logic into focused serializer functions with improved error handling.
Description check ✅ Passed The description provides a clear summary of changes, evidence of the problem addressed, and comprehensive test results matching the template structure with all required sections completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@shootingallday shootingallday left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers from my review on current head 5132409b3f0a856cd5f184f27fb59b5dc1133210.

Evidence checked:

  • Inspected app/activity.py, the corresponding app/main.py imports/call sites, and tests/test_activity_helpers.py.
  • Compared the moved activity/account accepted-work helper logic against origin/main; the row shaping, query filtering, contributor aggregation, account summary, accepted-work list, and fallback helpers appear preserved behind the new app.activity module boundary.
  • Verified the route layer still calls the same helper behavior for /api/v1/activity, account API accepted-work summaries, account accepted-work details, and the account page fallback paths.

Validation run locally:

  • uv run --extra dev python -m pytest tests/test_activity_helpers.py tests/test_activity.py tests/test_api_mcp.py -q -> 72 passed.
  • uv run --extra dev ruff check app/activity.py app/main.py tests/test_activity_helpers.py -> passed.
  • uv run --extra dev ruff format --check app/activity.py app/main.py tests/test_activity_helpers.py -> passed.
  • uv run --extra dev python -m mypy app -> success.
  • python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check origin/main...HEAD -> clean.

Residual risk is limited to the existing broad except Exception fallback behavior, which this PR moves unchanged rather than expanding.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/activity.py`:
- Around line 17-20: Wrap the json.loads(proof.public_json) call in
_activity_row in a try/except that catches json.JSONDecodeError and TypeError
(and any safe parsing errors), and defensively return None if parsing fails or
the result is not a dict; apply the same defensive parsing pattern to the other
helper that decodes proof.public_json (the helper around the other json.loads
usage) so malformed proof.public_json values are skipped instead of raising.

In `@tests/test_activity_helpers.py`:
- Around line 11-20: Add a DB-failure assertion for
safe_account_accepted_summary: in the
test_activity_helper_fallbacks_keep_account_schema function, after the existing
assertions, call safe_account_accepted_summary(BrokenSession(), "github:alice")
and assert it equals empty_accepted_summary(); this mirrors the existing
safe_accepted_work_for_account(BrokenSession(), ...) check and verifies the
fallback schema for safe_account_accepted_summary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dfac8525-c585-4648-a5a7-ace7f9dec52d

📥 Commits

Reviewing files that changed from the base of the PR and between 55608e3 and 5132409.

📒 Files selected for processing (3)
  • app/activity.py
  • app/main.py
  • tests/test_activity_helpers.py

Comment thread app/activity.py Outdated
Comment thread tests/test_activity_helpers.py Outdated
@ayskobtw-lil
Copy link
Copy Markdown
Contributor Author

Fixed the actionable review feedback in commit 2da5b66.

Changed:

  • Added shared defensive proof-public-json parsing for accepted activity helpers.
  • Malformed or non-dict proof.public_json values are now skipped instead of raising from _activity_row() or accepted_work_for_account().
  • Added the missing safe_account_accepted_summary() DB-failure assertion.
  • Added a malformed-proof regression covering activity_to_dict(), account_accepted_summary(), and accepted_work_for_account().

Validation:

  • python -m pytest tests/test_activity_helpers.py tests/test_activity.py tests/test_api_mcp.py -q -> 73 passed.
  • python -m ruff check app/activity.py app/main.py tests/test_activity_helpers.py -> all checks passed.
  • python -m ruff format --check app/activity.py app/main.py tests/test_activity_helpers.py -> already formatted.
  • python -m mypy app -> success.
  • python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check -> clean.

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 25, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 25, 2026

Holding this for a rebase/retarget after merged PR #330. The accepted serializer extraction now owns the overlapping accepted-work serialization boundary in app/serializers.py, so this needs an updated diff that avoids duplicating or moving the same helpers.

@ayskobtw-lil
Copy link
Copy Markdown
Contributor Author

Resolved the merge conflict after #329/#330 landed on main.

What changed in the conflict resolution:

  • Dropped the now-superseded standalone app/activity.py helper module and reused the merged app/serializers.py boundary.
  • Kept the useful hardening from this PR by moving malformed proof.public_json handling into app/serializers.py.
  • Added serializer regressions for DB-failure fallback shape and malformed public proof payloads.

Verification after the merge:

  • python -m pytest tests/test_serializers.py tests/test_activity.py tests/test_api_mcp.py -q -> 80 passed
  • python -m ruff check app/main.py app/serializers.py tests/test_serializers.py -> passed
  • python -m ruff format --check app/main.py app/serializers.py tests/test_serializers.py -> passed
  • python -m mypy app -> passed
  • python scripts/docs_smoke.py -> passed
  • git diff --check -> clean

Copy link
Copy Markdown
Contributor

@tolga-tom-nook tolga-tom-nook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers found.

Evidence checked on head ee15d046933272d5d645a5c2b5f9d16a3b7ea15d:

  • The new _proof_payload() helper centralizes bounty-payment proof JSON parsing and returns None for malformed/non-bounty payloads before both activity-feed and account accepted-work serializers use the data.
  • _activity_row() now reuses the same payload guard as accepted_work_for_account(), so a malformed Proof.public_json row is skipped consistently instead of breaking /activity or account accepted-work surfaces.
  • The regression mutates a real paid proof row to malformed JSON and verifies all three public serializer paths preserve stable empty shapes: activity_to_dict(), account_accepted_summary(), and accepted_work_for_account().
  • The safe wrapper fallback test still confirms database failures return the documented empty accepted-work shapes.

Validation I ran locally:

./.venv/bin/python -m pytest tests/test_serializers.py -q
# 4 passed
./.venv/bin/python -m ruff check app/serializers.py tests/test_serializers.py
# All checks passed
./.venv/bin/python -m mypy app
# Success: no issues found in 13 source files
git diff --check origin/main...HEAD
# clean

This is a focused code-health hardening of accepted-work/activity serialization with no public shape changes beyond avoiding malformed-proof crashes.

Copy link
Copy Markdown

@Karry2019web Karry2019web left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #328 Review — Extract accepted activity helpers

Scope: Inline code health improvements in app/serializers.py + test coverage for fallback behavior.

Files Inspected

File Lines Role
app/serializers.py 127-316, proposed diff +14/-4 Central change: extract _proof_payload() helper, use it in _activity_row() and accepted_work_for_account()
app/main.py 1092-1095 (api_activity route) Verifies the route calls activity_to_dict(session, q) — no change needed
app/activity.py Not in main branch — PR claims extraction target But the actual diff only touches serializers.py. The app/activity.py extraction referenced in the PR body does not appear in the diff.
tests/test_serializers.py +56/-1 diff New tests for BrokenSession fallback and malformed proof skipping
tests/test_activity.py 223 lines (existing) Existing route-level tests — confirms activity_to_dict behavior is unchanged

Correctness Analysis

1. _proof_payload() consolidation — correct

The original code in _activity_row():

data = json.loads(proof.public_json)
if not isinstance(data, dict) or data.get("kind") != "bounty_payment":
    return None

And in accepted_work_for_account():

data = json.loads(proof.public_json)
if not isinstance(data, dict) or data.get("kind") != "bounty_payment":
    continue

The new _proof_payload() wraps both calls:

def _proof_payload(proof: Proof) -> dict[str, Any] | None:
    try:
        data = json.loads(proof.public_json)
    except (TypeError, json.JSONDecodeError):
        return None
    if not isinstance(data, dict) or data.get("kind") != "bounty_payment":
        return None
    return data

✅ The behavior is preserved with an improvement: TypeError and JSONDecodeError are caught (the original code would crash on None or malformed public_json).

2. _activity_row() behavior — identical output where input is valid

The call sites use _proof_payload(proof) and check if data is None: return None — same logic as if data is None: continue in the second call site. Output shape is preserved.

3. Tests — adequate for the change

  • test_activity_serializer_fallbacks_keep_account_schema: Uses a BrokenSession that raises on any database access — verifies safe_* wrappers return empty fallback shapes. ✅
  • test_activity_serializers_skip_malformed_public_proofs: Creates a real bounty, pays with a malformed proof, then checks accepted_work_for_account and account_accepted_summary skip it gracefully. ✅

Minor Concern

The PR body claims it extracts code into app/activity.py, but the actual diff only changes app/serializers.py and tests/test_serializers.py. The app/activity.py extraction either:

  • Was already done in a prior commit on this branch
  • Or is aspirational but not included

The diff as presented is self-contained and correct — the _proof_payload() extraction is a genuine code health improvement that reduces duplication. The body/filename mismatch is harmless but worth noting.

Verdict

APPROVE — no blockers. The change is a focused, safe consolidation that:

  1. Removes duplicated json.loads + type-check code
  2. Adds graceful handling of edge cases (None, malformed JSON)
  3. Has regression tests for both the fallback path and real malformed data
  4. Preserves all existing behavior and public API shapes

The dirty mergeable state is a separate concern (the branch needs rebasing onto main) and does not reflect on the code quality of the change itself.

Copy link
Copy Markdown

@s0584273828-ctrl s0584273828-ctrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on PR #328 after rechecking current head ee15d046933272d5d645a5c2b5f9d16a3b7ea15d against current origin/main.

The serializer helper extraction itself looks focused, and the focused local checks below pass. The blocker is integration readiness: the branch is currently not mergeable with origin/main.

Evidence:

  • gh api repos/ramimbo/mergework/pulls/328 reports mergeable: false and mergeable_state: dirty.
  • git merge-tree --write-tree origin/main HEAD exits 1 and reports a content conflict in tests/test_serializers.py.
  • The command also auto-merges app/serializers.py, so the observed blocker is specifically the test-file conflict.

Validation on the current PR head:

  • uv run --extra dev python -m pytest tests/test_serializers.py -q -> 4 passed
  • uv run --extra dev ruff check app/serializers.py tests/test_serializers.py -> all checks passed
  • uv run --extra dev python -m mypy app/serializers.py -> no issues in 1 source file

Patch direction: rebase or merge current origin/main, resolve tests/test_serializers.py, and keep both the current-main serializer assertions and this PR's malformed-proof fallback regression coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants