Skip to content

tests: Add extensive tests for ArchivedPlugin.restore#54

Merged
fsbraun merged 3 commits intodjango-cms:masterfrom
mrbazzan:add_tests
Dec 5, 2025
Merged

tests: Add extensive tests for ArchivedPlugin.restore#54
fsbraun merged 3 commits intodjango-cms:masterfrom
mrbazzan:add_tests

Conversation

@mrbazzan
Copy link
Contributor

@mrbazzan mrbazzan commented Nov 24, 2025

Description

  • Update codebase to use helpers from django-cms
  • Add more tests for ArchivedPlugin.restore
  • I have opened this pull request against master
  • I have added or modified the tests when changing logic
  • I have followed the conventional commits guidelines to add meaningful information into the changelog
  • I have read the contribution guidelines and I have joined #workgroup-pr-review on
    Discord to find a “pr review buddy” who is going to review my pull request.

Summary by Sourcery

Strengthen ArchivedPlugin restore behavior and align plugin model lookup with django CMS helpers.

Enhancements:

  • Replace local plugin model resolution with django CMS's built-in get_plugin_model helper.

Tests:

  • Add comprehensive tests for ArchivedPlugin.restore covering many-to-many and foreign key fields, including a skipped case documenting current failure behavior.
  • Introduce a dedicated test app with Article and Section plugin models to support the new restore tests and register corresponding CMS plugins.
  • Update test settings to include the new test application.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 24, 2025

Reviewer's Guide

Adds targeted regression tests for ArchivedPlugin.restore, introduces a minimal test app with plugin models to exercise FK and M2M relations, and switches internal plugin model resolution to use django-cms’s built-in helper instead of local utilities.

Sequence diagram for ArchivedPlugin.restore using django CMS get_plugin_model

sequenceDiagram
    actor TestCase
    participant ArchivedPlugin
    participant CmsPluginsUtils as cms_utils_plugins
    participant PluginModel
    participant Database

    TestCase->>ArchivedPlugin: restore(target_placeholder_id, parent_plugin_id)
    activate ArchivedPlugin

    ArchivedPlugin->>CmsPluginsUtils: get_plugin_model(plugin_type)
    activate CmsPluginsUtils
    CmsPluginsUtils-->>ArchivedPlugin: PluginModel
    deactivate CmsPluginsUtils

    ArchivedPlugin->>PluginModel: construct_from_archived_data(serialized_data)
    activate PluginModel
    PluginModel->>Database: INSERT plugin row
    Database-->>PluginModel: plugin_pk

    PluginModel->>Database: RESTORE foreign_key_relations()
    PluginModel->>Database: RESTORE many_to_many_relations()

    PluginModel-->>ArchivedPlugin: restored_plugin_instance
    deactivate PluginModel

    ArchivedPlugin-->>TestCase: restored_plugin_instance
    deactivate ArchivedPlugin
Loading

Entity relationship diagram for test app plugin models with FK and M2M

erDiagram
    CMSPlugin {
        int id
        int placeholder_id
        int parent_id
        string language
        int position
    }

    FKPluginModel {
        int id
        int cmsplugin_ptr_id
        int related_model_id
    }

    RelatedModel {
        int id
    }

    M2MPluginModel {
        int id
        int cmsplugin_ptr_id
    }

    TagModel {
        int id
    }

    M2MPluginModel_Tags {
        int id
        int m2m_plugin_model_id
        int tag_model_id
    }

    CMSPlugin ||--o| FKPluginModel : one_to_one_inheritance
    CMSPlugin ||--o| M2MPluginModel : one_to_one_inheritance

    FKPluginModel }o--|| RelatedModel : foreign_key

    M2MPluginModel ||--o{ M2MPluginModel_Tags : join_table
    TagModel ||--o{ M2MPluginModel_Tags : join_table
Loading

Updated class diagram for ArchivedPlugin and plugin model resolution

classDiagram
    class UtilsModule {
        +get_related_fields(model)
        +get_plugin_fields(plugin_type)
    }

    class CmsPluginsUtils {
        +get_plugin_model(plugin_type)
    }

    class CMSPlugin {
        +id
        +placeholder_id
        +parent_id
        +language
        +position
    }

    class ArchivedPlugin {
        +plugin_type
        +serialized_data
        +language
        +position
        +placeholder_id
        +parent_id
        +restore(target_placeholder_id, parent_plugin_id)
    }

    class FKPluginModel {
        +foreign_object
    }

    class M2MPluginModel {
        +related_objects
    }

    class RelatedModel {
        +id
    }

    class TagModel {
        +id
    }

    FKPluginModel --|> CMSPlugin
    M2MPluginModel --|> CMSPlugin

    FKPluginModel --> RelatedModel : foreign_key
    M2MPluginModel --> TagModel : many_to_many

    ArchivedPlugin ..> CmsPluginsUtils : uses
    CmsPluginsUtils ..> FKPluginModel : returns
    CmsPluginsUtils ..> M2MPluginModel : returns
    UtilsModule ..> CMSPlugin : introspects
    UtilsModule ..> FKPluginModel : introspects
    UtilsModule ..> M2MPluginModel : introspects
Loading

File-Level Changes

Change Details Files
Add regression tests covering ArchivedPlugin.restore behavior with FK and M2M-related fields, including a skipped test for a known failure case.
  • Create tests that restore an ArticlePlugin with a ManyToManyField and assert copied fields, PK, and position.
  • Create tests that restore a RandomPlugin with an existing ForeignKey-related object and assert field equality and positioning.
  • Add a skipped test documenting the current failure when restoring a plugin whose serialized data omits a required related field, expecting the restored field to be None instead of raising DoesNotExist.
  • Import the Section model from the new test app into the import tests module.
tests/test_import.py
Replace local plugin model lookup helpers with django-cms’s built-in get_plugin_model utility.
  • Remove cached get_plugin_class and get_plugin_model helpers and the now-unused get_plugin_fields helper from the utils module.
  • Update ArchivedPlugin implementation to import get_plugin_model from cms.utils.plugins instead of the local utils module.
djangocms_transfer/utils.py
djangocms_transfer/datastructures.py
Introduce a minimal test app providing plugin models with FK and M2M relations for use in tests.
  • Register the tests.test_app Django application in test settings so its models and plugins are available during tests.
  • Define Article and ArticlePluginModel CMSPlugin subclasses with ForeignKey and ManyToMany relationships to a Section model.
  • Register RandomPlugin and ArticlePlugin CMSPluginBase plugins for the new models so they can be created via the CMS plugin APIs in tests.
tests/settings.py
tests/test_app/models.py
tests/test_app/cms_plugins.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The tests that assert hard-coded primary keys (e.g., expecting restored_plugin.pk == 2) are brittle and may become flaky depending on database state; consider asserting relative behavior (such as existence, equality with original fields, or count of plugins) instead of specific PK values.
  • In the skipped test for non-existing related fields you expect section to be None, but the Article.section field is non-nullable (no null=True), so either the test expectation or the model definition should be adjusted to match the intended behavior before enabling this test.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The tests that assert hard-coded primary keys (e.g., expecting restored_plugin.pk == 2) are brittle and may become flaky depending on database state; consider asserting relative behavior (such as existence, equality with original fields, or count of plugins) instead of specific PK values.
- In the skipped test for non-existing related fields you expect section to be None, but the Article.section field is non-nullable (no null=True), so either the test expectation or the model definition should be adjusted to match the intended behavior before enabling this test.

## Individual Comments

### Comment 1
<location> `tests/test_import.py:29-32` </location>
<code_context>
+        placeholder = self.page_content.placeholders.create(slot="test")
+        restored_plugin = archived_plugin.restore(placeholder, "en")
+        self.assertEqual(restored_plugin.title, "Test")
+        # second ArticlePluginModel instance in a new placeholder
+        self.assertEqual(restored_plugin.pk, 2)
+        self.assertEqual(restored_plugin.position, 1)
+        self.assertEqual(
</code_context>

<issue_to_address>
**suggestion (testing):** Avoid asserting a hard-coded primary key value in tests

Asserting `restored_plugin.pk == 2` makes this test brittle because it depends on database state and plugin creation order. Instead, consider asserting that the restored plugin has a different pk than the original (e.g. `assertNotEqual(restored_plugin.pk, plugin.pk)`) or that it appears in the expected queryset/placeholder. This will keep the test stable across environments and future fixture changes.

```suggestion
        self.assertEqual(restored_plugin.title, "Test")
        # restored plugin should be a new instance in the new placeholder
        self.assertNotEqual(restored_plugin.pk, plugin.pk)
        self.assertEqual(restored_plugin.placeholder, placeholder)
        self.assertEqual(restored_plugin.position, 1)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Nov 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.70%. Comparing base (8dfd289) to head (303d9a4).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #54      +/-   ##
==========================================
+ Coverage   84.92%   91.70%   +6.78%     
==========================================
  Files          10       10              
  Lines         398      398              
  Branches       56       56              
==========================================
+ Hits          338      365      +27     
+ Misses         49       22      -27     
  Partials       11       11              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

return [field.name for field in fields if field.is_relation]


@lru_cache()
Copy link
Member

Choose a reason for hiding this comment

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

This is actually the "official" way to get a plugin class. I would keep it instead of using the utils which might change in the future...

Copy link
Contributor Author

@mrbazzan mrbazzan Dec 3, 2025

Choose a reason for hiding this comment

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

Alright then.
@fsbraun Even for the get_plugin_model too right?

Copy link
Member

@fsbraun fsbraun left a comment

Choose a reason for hiding this comment

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

Nice work, @mrbazzan !

@mrbazzan
Copy link
Contributor Author

mrbazzan commented Dec 5, 2025

Nice work, @mrbazzan !

@fsbraun Ready for another review

Copy link
Member

@fsbraun fsbraun left a comment

Choose a reason for hiding this comment

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

👏

@fsbraun fsbraun merged commit dad634c into django-cms:master Dec 5, 2025
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants