Skip to content

Add comprehensive Plone integration tests for move optimization#33

Open
jensens wants to merge 11 commits intomainfrom
feature/plone-integration-tests
Open

Add comprehensive Plone integration tests for move optimization#33
jensens wants to merge 11 commits intomainfrom
feature/plone-integration-tests

Conversation

@jensens
Copy link
Member

@jensens jensens commented Mar 17, 2026

Summary

  • 16 new Plone integration tests exercising real OFS operations via plone.app.testing
  • Tests cover: copy vs move distinction, deep nesting, sequential operations, many-children scalability, security reindex behavior, stack integrity, passthrough mode, negative depth deltas
  • All 1073 tests pass locally with 92.42% coverage

Test plan

  • All new tests pass locally against real Plone layer
  • Coverage stays above 90% threshold
  • CI green on Python 3.12 and 3.13

🤖 Generated with Claude Code

jensens and others added 3 commits March 18, 2026 00:35
16 new tests exercising real OFS operations in a Plone site:
- Copy/paste does not trigger optimization (2 tests)
- Deep nested folder rename with single pending move
- Physical path correctness after rename
- Multiple sequential renames accumulate pending moves
- Rename-then-move composition (2 pending moves)
- Move folder with 20 children (scalability)
- Add content does not trigger optimization
- Security reindex called only for cross-container moves (2 tests)
- Move context stack integrity across all operations
- Stack survives failed rename
- Move to deeply nested target (depth delta +3)
- Passthrough behavior without pgcatalog (2 tests)
- Move up in tree (negative depth delta)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests IndexRegistry.sync_from_catalog, index extraction from real
Plone content, _set_pg_annotation pipeline, setuphandlers (snapshot,
replace, restore), value conversion, tool API, and lexicon cleanup.

Move shared zope.pytestlayer fixture creation to conftest.py so both
test_plone_integration.py and test_move_integration.py share the same
Plone layer fixtures without fixture-not-found errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 25 end-to-end tests using PGJsonbStorage that verify data actually
reaches PostgreSQL:
- Catalog columns (path, idx, searchable_text) written correctly
- Nested content hierarchy with correct parent_path and depth
- Rename updates all descendant paths in PG
- Move between containers updates paths and depth
- Searchable text preserved across renames
- Sibling objects unaffected by move
- PG snapshot isolation between tests (rows rolled back)
- SQL queryability via JSONB operators and path prefix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jensens jensens force-pushed the feature/plone-integration-tests branch from a73c3c6 to f7eae02 Compare March 17, 2026 23:42
jensens and others added 8 commits March 18, 2026 00:52
PGCatalogPGFixture registered PlonePGCatalogTool as IPGCatalogTool
utility but left the standard CatalogTool as portal_catalog. This
meant catalog_object() went through ZCatalog (writing to BTrees),
never calling _set_pg_annotation(), so CatalogStateProcessor never
wrote path/idx/searchable_text columns to PG.

Now calls _replace_catalog() + _ensure_catalog_indexes() after
addPloneSite() to swap in PlonePGCatalogTool as the active
ICatalogTool, enabling the full PG annotation pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PGCatalogPGFixture fired IDatabaseOpenedWithRoot via notify() but
no subscriber was registered — configure.zcml is never loaded in the
PG fixture (only in the DemoStorage-based PGCatalogLayer). This meant
CatalogStateProcessor was never registered on the storage, so catalog
columns were never written to PG.

Fix: import and call register_catalog_processor() directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trigger push only on main (post-merge) instead of branches-ignore.
Previously both push and pull_request fired for PR branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CatalogStateProcessor was not declaring or returning parent_path
and path_depth as extra columns, so they stayed NULL in object_state
when written via the ZODB commit path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests end-to-end with real PostgreSQL + Plone:
- searchResults() via catalog API (type queries, multi-type, empty)
- Uncatalog/delete flow (NULL columns, sibling preservation)
- Partial reindex via reindexObject(idxs=[...])
- Full-text search with tsvector weight ranking (Title > Description)
- Path queries (subtree, depth=1, exact match)
- Sort + pagination (sort_on, sort_order, b_start/b_size)
- Brain attributes (getPath, Title, portal_type, getObject, getRID)
- Security filtering (Manager vs anonymous, mixed visibility, unrestricted)
- uniqueValuesFor() with real PG data
- Maintenance ops (refreshCatalog, clearFindAndRebuild, manage_catalogClear)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Partial reindex tests → full reindex (setTitle marks dirty, partial
  path conflicts with state processor re-serialization)
- Security tests: two-phase commit (create first, restrict second)
- uniqueValuesFor: use portal_type (field index) not Subject (keyword array)
- refreshCatalog: avoid intermediate commit that NULLs idx
- manage_catalogClear: verify via direct PG query (connection context)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The manage_permission approach didn't reliably update allowedRolesAndUsers
in the PG idx. Use future effective_date instead — anonymous users are
filtered by effectiveRange (can't see future-dated content), while
Managers have AccessInactivePortalContent and can.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant