Skip to content

Refactor tests#32

Merged
chris-adam merged 1 commit intomainfrom
PARAF-360/esign_tests
May 5, 2026
Merged

Refactor tests#32
chris-adam merged 1 commit intomainfrom
PARAF-360/esign_tests

Conversation

@chris-adam
Copy link
Copy Markdown
Contributor

@chris-adam chris-adam commented Mar 27, 2026

Summary by CodeRabbit

  • Tests
    • Major test-suite reorganization with shared fixtures and many new/expanded tests covering sessions, browser views, settings, services, and file actions.
  • Bug Fixes
    • More robust external session creation/feedback and improved file/session handling to avoid missing/duplicate files and incorrect session states.
  • Refactor
    • Centralized test setup and helpers for more maintainable, consistent test behavior.

@chris-adam chris-adam marked this pull request as draft March 27, 2026 12:41
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Replace per-test manual setup with a shared BaseEsignTest and seeded fixtures; refactor many tests to use the base, add/reshape tests for external session creation, feedback POST, VAT validation, and removal flows; adjust create_external_session to use per-file fuid for document identifiers and a minor formatting change in events.

Changes

Test base + test refactor + utils

Layer / File(s) Summary
Test base & fixtures
src/imio/esign/tests/base.py, src/imio/esign/testing.py
Add BaseEsignTest (integration layer), authenticate test user in setup, and seed content categories/folders/12 annex PDFs via new helpers _setup_content_categories, _setup_shared_content, and _create_annex.
Core test wiring
src/imio/esign/tests/*, src/imio/esign/tests/test_utils.py
Refactor many test modules to inherit BaseEsignTest and rely on seeded fixtures; simplify/rename several tests (notably test_add_files_to_session, consolidated test_create_external_session, added test_remove_files_from_session) and add new modules/tests (test_browser_settings.py, test_services.py).
View/test behavioral changes
src/imio/esign/tests/test_actions.py, src/imio/esign/tests/test_browser_views.py, src/imio/esign/tests/test_actions.py
Instantiate view classes directly in tests, adjust assertions to inspect session annotations rather than status messages/redirect internals, add _clear_status_messages(request) helper and viewlet/session-listing tests.
External session payload logic
src/imio/esign/utils.py
create_external_session now uses the per-file tuple UID (fuid) for commonData.documentData.uniqueCode and docUuid (via get_suid_from_uuid(fuid)); transmitted multipart files ignore the tuple UID (_uid). Tests cover the _no_files_ sentinel path.
Event minor formatting
src/imio/esign/events.py
Remove a line-continuation in an if expression comparing annex vs file_info (no logic change).
Tests added/expanded
src/imio/esign/tests/test_browser_settings.py, src/imio/esign/tests/test_services.py
Add VAT validator tests and ExternalSessionFeedbackPost tests including auth checks and many feedback-code state transitions.
Setup/test housekeeping
src/imio/esign/tests/test_setup.py
Minor docstring/comment reorganizations in install/uninstall tests.

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • IMIO/imio.esign#10 — Related refactor/centralization of tests and remove-item/remove-from-session behavior exercised by updated tests.
  • IMIO/imio.esign#13 — Related changes to create_external_session file-resolution/error handling and _no_files_ sentinel.
  • IMIO/imio.esign#11 — Related external-session / seal-email handling and view-level permission behavior.

Suggested reviewers

  • gbastien
  • sgeulette

Poem

"🐰 I hopped through tests with a nibble and grin,
Seeds of annex PDFs tucked safely within.
Fuid finds its code, sessions tidy and neat,
Views trimmed, feedbacks checked — the suite is complete.
Time for a carrot — CI, make my day sweet!"

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Refactor tests" is generic and vague, using non-descriptive phrasing that doesn't convey the specific scope or nature of the test refactoring work. Consider a more specific title that indicates the primary refactoring goal, such as "Refactor tests to use shared BaseEsignTest fixture" or "Consolidate test setup and reduce test duplication".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch PARAF-360/esign_tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@coveralls
Copy link
Copy Markdown

coveralls commented Mar 27, 2026

Coverage Report for CI Build 25375500821

Coverage increased (+7.9%) to 84.566%

Details

  • Coverage increased (+7.9%) from the base build.
  • Patch coverage: 2 of 2 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1257
Covered Lines: 1063
Line Coverage: 84.57%
Coverage Strength: 0.85 hits per line

💛 - Coveralls

@chris-adam chris-adam force-pushed the PARAF-360/esign_tests branch 5 times, most recently from 8730123 to 27fc56f Compare April 1, 2026 12:39
@chris-adam chris-adam marked this pull request as ready for review April 1, 2026 12:40
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.

🧹 Nitpick comments (3)
src/imio/esign/tests/test_utils.py (1)

474-479: Lock in the multipart files= shape as well.

These assertions only cover the JSON body. The production change also switched requests.post(..., files=...) to a list of repeated "files" tuples so duplicate filenames survive, and that behavior is still untested here. Add one assertion on mock_post.call_args[1]["files"] so this regression path stays covered.

Example assertion to add next to the first payload check
+        self.assertEqual(
+            [(field, filename) for field, (filename, _content) in mock_post.call_args[1]["files"]],
+            [("files", "annex1.pdf"), ("files", "annex2.pdf")],
+        )

Also applies to: 512-517, 554-557

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/imio/esign/tests/test_utils.py` around lines 474 - 479, The test
currently only asserts the JSON payload from create_external_session; add an
assertion that mock_post.call_args[1]["files"] is the expected multipart shape
(a list of ("files", (filename, content, content_type)) tuples) to confirm
duplicate filenames survive and the code sends a list of repeated "files"
tuples; update the assertion next to the existing payload check in test_utils.py
(around the create_external_session call) to validate
mock_post.call_args[1]["files"] is a list and contains the expected repeated
filename entries (and apply the same additional files assertion at the other two
test locations referenced in the comment).
src/imio/esign/tests/base.py (1)

3-5: Reset the default login in the shared base setup.

This base is now reused across the suite, but setUp() only clears request.form and resets roles. Some tests switch principals with login(..., "admin")/logout(), so if that shared security context persists between methods the permission checks become order-dependent. Re-logging the default test user here would make the isolation explicit.

Small base-setup reset
 from imio.esign.testing import IMIO_ESIGN_INTEGRATION_TESTING
+from plone.app.testing import login
 from plone.app.testing import setRoles
 from plone.app.testing import TEST_USER_ID
+from plone.app.testing import TEST_USER_NAME
@@
     def setUp(self):
         self.portal = self.layer["portal"]
         self.request = self.layer["request"]
         self.request.form.clear()
         setRoles(self.portal, TEST_USER_ID, ["Manager"])
+        login(self.portal, TEST_USER_NAME)

Also applies to: 19-23

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/imio/esign/tests/base.py` around lines 3 - 5, The shared test base's
setUp currently only clears request.form and resets roles, which can leave a
switched-in principal from a previous test; update setUp to explicitly re-login
the default test user by calling the plone testing login with TEST_USER_ID (and
then restore roles via setRoles as needed) so the security context is
deterministic; locate the setUp method in the base test class and add a
login(self.portal, TEST_USER_ID) (importing login if missing) immediately after
the request/roles reset to ensure each test starts with the default principal.
src/imio/esign/testing.py (1)

77-82: Own this fixture instead of reading collective.iconifiedcategory's test data.

setUpPloneSite() now depends on collective.iconifiedcategory/tests/icône1.png, which is an internal test asset. If that file is missing from the installed distribution, the whole layer dies before any test runs. Please move the icon into this repo (or build a tiny blob inline) so the suite only depends on repo-owned fixtures.

Minimal path change
-        icon_path = os.path.join(
-            os.path.dirname(collective.iconifiedcategory.__file__),
-            "tests",
-            u"icône1.png",
-        )
+        icon_path = os.path.join(os.path.dirname(__file__), "tests", "icon.png")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/imio/esign/testing.py` around lines 77 - 82, The fixture currently reads
an external test asset via icon_path in setUpPloneSite(), which makes the layer
depend on collective.iconifiedcategory; instead, add and use a repo-owned
fixture: either add the icône1.png into this repository (e.g., tests/assets/)
and change the icon_path construction to join os.path.dirname(__file__) with
that local assets path (update the icon_path variable usage), or embed a small
PNG as a base64 blob in testing.py and decode it to bytes where the file is
opened (replace the with open(icon_path, "rb") block to use the in-repo file or
decoded bytes). Ensure references are to the icon_path variable and the
setUpPloneSite() flow so the layer no longer depends on external package test
data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/imio/esign/testing.py`:
- Around line 77-82: The fixture currently reads an external test asset via
icon_path in setUpPloneSite(), which makes the layer depend on
collective.iconifiedcategory; instead, add and use a repo-owned fixture: either
add the icône1.png into this repository (e.g., tests/assets/) and change the
icon_path construction to join os.path.dirname(__file__) with that local assets
path (update the icon_path variable usage), or embed a small PNG as a base64
blob in testing.py and decode it to bytes where the file is opened (replace the
with open(icon_path, "rb") block to use the in-repo file or decoded bytes).
Ensure references are to the icon_path variable and the setUpPloneSite() flow so
the layer no longer depends on external package test data.

In `@src/imio/esign/tests/base.py`:
- Around line 3-5: The shared test base's setUp currently only clears
request.form and resets roles, which can leave a switched-in principal from a
previous test; update setUp to explicitly re-login the default test user by
calling the plone testing login with TEST_USER_ID (and then restore roles via
setRoles as needed) so the security context is deterministic; locate the setUp
method in the base test class and add a login(self.portal, TEST_USER_ID)
(importing login if missing) immediately after the request/roles reset to ensure
each test starts with the default principal.

In `@src/imio/esign/tests/test_utils.py`:
- Around line 474-479: The test currently only asserts the JSON payload from
create_external_session; add an assertion that mock_post.call_args[1]["files"]
is the expected multipart shape (a list of ("files", (filename, content,
content_type)) tuples) to confirm duplicate filenames survive and the code sends
a list of repeated "files" tuples; update the assertion next to the existing
payload check in test_utils.py (around the create_external_session call) to
validate mock_post.call_args[1]["files"] is a list and contains the expected
repeated filename entries (and apply the same additional files assertion at the
other two test locations referenced in the comment).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b922db63-25f7-43c7-9298-f1c3b7dd77cd

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca5416 and 27fc56f.

📒 Files selected for processing (10)
  • src/imio/esign/events.py
  • src/imio/esign/testing.py
  • src/imio/esign/tests/base.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_browser_settings.py
  • src/imio/esign/tests/test_browser_views.py
  • src/imio/esign/tests/test_services.py
  • src/imio/esign/tests/test_setup.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py
✅ Files skipped from review due to trivial changes (2)
  • src/imio/esign/tests/test_setup.py
  • src/imio/esign/events.py

@chris-adam chris-adam force-pushed the PARAF-360/esign_tests branch from 27fc56f to bbfe323 Compare April 1, 2026 13:08
@chris-adam chris-adam requested review from gbastien and sgeulette April 1, 2026 13:16
@chris-adam
Copy link
Copy Markdown
Contributor Author

Je vais rebase et merge cette PR parce que ça me bloque pour pouvoir continuer le dev

@chris-adam chris-adam force-pushed the PARAF-360/esign_tests branch from bbfe323 to 5b25970 Compare May 5, 2026 12:09
@chris-adam chris-adam force-pushed the PARAF-360/esign_tests branch from 5b25970 to 31e422c Compare May 5, 2026 12:09
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.

🧹 Nitpick comments (4)
src/imio/esign/tests/test_services.py (2)

155-170: ⚡ Quick win

Fully assert the returns tuple shape (including datetime slot).

The comment says (code, db_state, value, message, datetime), but the test only validates the first four fields. Add a shape check so a dropped datetime value is caught.

Suggested patch
         entry = srtn["returns"][0]
+        self.assertEqual(len(entry), 5)
         self.assertEqual(entry[0], 23)
         self.assertEqual(entry[1], "completed")
         self.assertEqual(entry[2], {"info": "ok"})
         self.assertEqual(entry[3], "All done")
+        self.assertIsNotNone(entry[4])
🤖 Prompt for 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.

In `@src/imio/esign/tests/test_services.py` around lines 155 - 170, The test
currently checks only the first four slots of the returns tuple from
add_files_to_session (variable srtn and entry) but omits the datetime slot;
update the assertions for srtn["returns"][0] (entry) to assert the tuple length
is 5 and that the fifth element is a datetime (e.g., an instance of
datetime.datetime) before existing assertions so a missing or dropped datetime
will fail; locate the checks around add_files_to_session and self._reply in
test_services.py and add the length + type assertion immediately after assigning
entry.

63-77: ⚡ Quick win

Add one end-to-end unauthorized reply() assertion.

test_reply() currently goes through _reply(), which forces verify_auth_token=True, so the auth gate in reply() is never exercised in this test method. Add one negative-auth call to catch regressions where reply() stops enforcing authorization.

Suggested patch
 def test_reply(self):
     """reply() validates auth/input, processes all feedback codes, updates session state."""
     annex1 = self.portal["folder0"]["annex2"]
+
+    # unauthorized request should be rejected by reply()
+    self.request.set("BODY", json.dumps({"app_session_id": self.sign_id, "code": 21}))
+    with patch("imio.esign.services.external_session_feedback.verify_auth_token", return_value=False):
+        self._make_service().reply()
+    self.assertNotEqual(self.request.response.getStatus(), 200)
+    self.request.response.setStatus(200)
 
     # missing app_session_id
     result = self._reply({"code": 21})
🤖 Prompt for 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.

In `@src/imio/esign/tests/test_services.py` around lines 63 - 77, In test_reply(),
add an end-to-end unauthorized assertion that calls the real reply() flow
instead of the helper _reply() (which forces verify_auth_token=True); invoke
reply() (or the service endpoint used by reply()) with a payload like
{"app_session_id": <valid-or-sample>, "code": 21} but without any auth token and
assert the response status is 401/403 and the response message indicates
unauthorized (or contains "auth" / "unauthorized"); ensure you place this before
other state-changing assertions and reference the existing test method
test_reply, the helper _reply, and the reply() auth gate (verify_auth_token) so
the test fails if authorization stops being enforced.
src/imio/esign/tests/test_actions.py (2)

34-38: ⚡ Quick win

Factor duplicated test setup into a shared helper/base method.

The repeated user/signer/fixture initialization across three classes will drift over time. Moving this into BaseEsignTest (or a local helper) would reduce maintenance overhead.

Also applies to: 85-89, 115-123

🤖 Prompt for 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.

In `@src/imio/esign/tests/test_actions.py` around lines 34 - 38, Extract the
duplicated test setup (api.user.create(...), setting self.folder, self.annexes,
self.signers, and instantiating RemoveItemFromSessionView) into a reusable
helper method on BaseEsignTest (e.g., def setup_common_fixtures(self): ...) or a
module-level helper, then replace the duplicated blocks in each test class with
a single call to that helper; ensure the helper returns or assigns the same
attributes (folder, annexes, signers, view) so callers use the identical names
used in the tests (self.folder, self.annexes, self.signers, self.view) and
update imports if you move the helper to a separate module.

160-167: ⚡ Quick win

Avoid hardcoded absolute URLs in expected HTML assertions.

The assertions currently pin http://nohost/plone/...; deriving expected URLs from fixture objects would make tests less environment-coupled while keeping the same intent.

♻️ Suggested adjustment
-        self.assertEqual(
-            self.view._uid_to_link(uid),
-            u"<a href='http://nohost/plone/folder0/view' title='/plone/folder0'>Folder 0</a>",
-        )
+        expected_folder_link = u"<a href='{}/view' title='{}'>{}</a>".format(
+            self.folder.absolute_url(), self.folder.absolute_url_path(), self.folder.Title()
+        )
+        self.assertEqual(self.view._uid_to_link(uid), expected_folder_link)

Also applies to: 221-235

🤖 Prompt for 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.

In `@src/imio/esign/tests/test_actions.py` around lines 160 - 167, Tests assert
absolute HTML links using a hardcoded "http://nohost/plone/..." which couples
them to an environment; update the assertions in tests referencing _uid_to_link
(and the similar assertions at lines 221-235) to build the expected URL from the
test fixtures instead (e.g., use self.portal.absolute_url() or
self.view.context.absolute_url() / request.getURL() to derive the base and then
append the relative path) and compare against that generated URL in the expected
<a> tag or <span> output.
🤖 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.

Nitpick comments:
In `@src/imio/esign/tests/test_actions.py`:
- Around line 34-38: Extract the duplicated test setup (api.user.create(...),
setting self.folder, self.annexes, self.signers, and instantiating
RemoveItemFromSessionView) into a reusable helper method on BaseEsignTest (e.g.,
def setup_common_fixtures(self): ...) or a module-level helper, then replace the
duplicated blocks in each test class with a single call to that helper; ensure
the helper returns or assigns the same attributes (folder, annexes, signers,
view) so callers use the identical names used in the tests (self.folder,
self.annexes, self.signers, self.view) and update imports if you move the helper
to a separate module.
- Around line 160-167: Tests assert absolute HTML links using a hardcoded
"http://nohost/plone/..." which couples them to an environment; update the
assertions in tests referencing _uid_to_link (and the similar assertions at
lines 221-235) to build the expected URL from the test fixtures instead (e.g.,
use self.portal.absolute_url() or self.view.context.absolute_url() /
request.getURL() to derive the base and then append the relative path) and
compare against that generated URL in the expected <a> tag or <span> output.

In `@src/imio/esign/tests/test_services.py`:
- Around line 155-170: The test currently checks only the first four slots of
the returns tuple from add_files_to_session (variable srtn and entry) but omits
the datetime slot; update the assertions for srtn["returns"][0] (entry) to
assert the tuple length is 5 and that the fifth element is a datetime (e.g., an
instance of datetime.datetime) before existing assertions so a missing or
dropped datetime will fail; locate the checks around add_files_to_session and
self._reply in test_services.py and add the length + type assertion immediately
after assigning entry.
- Around line 63-77: In test_reply(), add an end-to-end unauthorized assertion
that calls the real reply() flow instead of the helper _reply() (which forces
verify_auth_token=True); invoke reply() (or the service endpoint used by
reply()) with a payload like {"app_session_id": <valid-or-sample>, "code": 21}
but without any auth token and assert the response status is 401/403 and the
response message indicates unauthorized (or contains "auth" / "unauthorized");
ensure you place this before other state-changing assertions and reference the
existing test method test_reply, the helper _reply, and the reply() auth gate
(verify_auth_token) so the test fails if authorization stops being enforced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54ab4a73-f0f0-40a4-a9b1-172be93bba0c

📥 Commits

Reviewing files that changed from the base of the PR and between bbfe323 and 31e422c.

⛔ Files ignored due to path filters (1)
  • src/imio/esign/tests/icône1.png is excluded by !**/*.png
📒 Files selected for processing (10)
  • src/imio/esign/events.py
  • src/imio/esign/testing.py
  • src/imio/esign/tests/base.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_browser_settings.py
  • src/imio/esign/tests/test_browser_views.py
  • src/imio/esign/tests/test_services.py
  • src/imio/esign/tests/test_setup.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py
✅ Files skipped from review due to trivial changes (4)
  • src/imio/esign/events.py
  • src/imio/esign/tests/base.py
  • src/imio/esign/tests/test_browser_settings.py
  • src/imio/esign/utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/imio/esign/testing.py
  • src/imio/esign/tests/test_utils.py

@chris-adam chris-adam merged commit 6232bea into main May 5, 2026
5 checks passed
@chris-adam chris-adam deleted the PARAF-360/esign_tests branch May 5, 2026 12:29
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