plone / plone/pytest-plone

@pytest.mark.portal(profiles=...) applies profiles without elevating, so an import step that creates content fails

Open
#63 0 comments 1 reaction 1 assignee View on GitHub

@ericof is already working on this.

Since Aug 21, 2026.

Dominant language
Python
Stars
5
Forks
1
PR merge metrics
No merged PRs in 30d

Description

Summary

@pytest.mark.portal(profiles=[...]) runs the GenericSetup import steps as whoever is currently logged in. plone.app.testing.applyProfile logs in as the site owner first. So an add-on whose import step creates content — a container, a folder of member data, a default document — works under applyProfile and fails under the marker with Unauthorized.

The roles=["Manager"] argument looks like the fix and is not, because roles are granted after profiles are applied.

Affects 1.1.0 (and main).

Reproduction

An import step that creates content:

def post_install(context):
    api.content.create(container=api.portal.get(), type="Folder", id="things", title="Things")
@pytest.mark.portal(profiles=["my.addon:default"])
def test_container_exists(portal):
    assert "things" in portal
AccessControl.unauthorized.Unauthorized: Cannot create Folder

Adding roles=["Manager"] does not help:

@pytest.mark.portal(profiles=["my.addon:default"], roles=["Manager"])
def test_container_exists(portal):
    assert "things" in portal

Same failure. I ran both against a real add-on: 12 errors either way, all Unauthorized: Cannot create Folder.

Cause

apply_portal_marker applies profiles first and grants roles last, so the grant lands after the import step that needed it (src/pytest_plone/fixtures/markers.py, 1.1.0):

with site(portal):
    if marker_profiles:
        apply_profiles(portal, marker_profiles)   # runs as the test user
    if marker_content:
        with api.env.adopt_user(SITE_OWNER_NAME):
            create_content(portal, marker_content)
    if marker_roles:
        grant_roles(portal, marker_roles)         # too late to help the line above

The grant does not carry over to the next test either — integration testing rolls the transaction back — so it cannot even succeed from the second test onwards.

Note that create_content on the line above already elevates with adopt_user(SITE_OWNER_NAME). apply_profiles is the one step in this function that does not, which is what makes the failure surprising.

For comparison, plone.app.testing.helpers.applyProfile:

sm = getSecurityManager()
zope.login(app["acl_users"], SITE_OWNER_NAME)
try:
    setup_tool.runAllImportStepsFromProfile(profileId, ...)
finally:
    setSecurityManager(sm)

Verification that elevation is the whole difference

I patched the installed apply_profiles to log in as the site owner around runAllImportStepsFromProfile, changing nothing else. The suite that produced the 12 errors went to 252 passed. Restored the file afterwards.

Suggested fix

Either is fine, and they are not exclusive:

  1. Elevate in apply_profiles, mirroring applyProfile. This is the narrower fix and matches what create_content already does two lines below.
  2. Reorder apply_portal_marker to roles → profiles → content. Then roles=["Manager"] behaves the way a reader expects, and a test that wants an unprivileged profile import can still get one by omitting the role. Content creation stays last, which is where it belongs.

Applying a GenericSetup profile is a manager's action in every real deployment — portal_setup requires Manage portal — so elevating by default seems right rather than surprising.

Workaround for anyone hitting this

Grant the role in an autouse fixture, so it lands before the marker runs:

@pytest.fixture(autouse=True)
def _manager(integration):
    setRoles(integration["portal"], TEST_USER_ID, ["Manager"])

Docs

docs/reference/markers.md documents the three arguments but not the order they are applied in, nor the security context profiles are applied under. Worth stating either way, since the order is what makes roles= not do what it looks like it does.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.