diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 81569399c7..32a5b3d1ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,5 +1,5 @@ name: Tests -on: [push,pull_request] +on: [push, pull_request] jobs: build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name @@ -8,19 +8,20 @@ jobs: fail-fast: false matrix: include: - - python-version: '3.7' - plone-version: '5.2' - - python-version: '3.8' - plone-version: '5.2' - - python-version: '3.8' - plone-version: '6.0' - - python-version: '3.9' - plone-version: '6.0' - - python-version: '3.10' - plone-version: '6.0' - - python-version: '3.11' - plone-version: '6.0' - + - python-version: "3.7" + plone-version: "5.2" + - python-version: "3.8" + plone-version: "5.2" + - python-version: "3.8" + plone-version: "6.0" + - python-version: "3.9" + plone-version: "6.0" + - python-version: "3.10" + plone-version: "6.0" + - python-version: "3.11" + plone-version: "6.0" + - python-version: "3.12" + plone-version: "6.0" steps: # git checkout - uses: actions/checkout@v3 @@ -30,7 +31,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - cache: 'pip' + cache: "pip" # buildout eggs cache - uses: actions/cache@v3 diff --git a/CHANGES.rst b/CHANGES.rst index 1513cd6cf2..2d63ccce38 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,36 @@ Changelog .. towncrier release notes start +9.1.0 (2023-10-18) +------------------ + +New features: + + +- Add support for Python 3.12. @tisto (#1722) + + +Bug fixes: + + +- Treat sub-items like items in ``@linkintegrity`` endpoint. @jaroel (#1714) +- Limits the use of multilingual services only if multilingual is actually installed. @mamico (#1723) + + +Internal: + + +- Remove unused code. @davisagli (#1703) +- Replace deprecated assert methods. @gforcada (#1719) +- Drop, already unused plone.app.robotframework test. @gforcada (#1720) + + +Documentation: + + +- Fix redirect for https://json-schema.org/. @stevepiercy (#1718) + + 9.0.0 (2023-09-23) ------------------ @@ -461,9 +491,9 @@ Bug fixes: - Added url field to Actions (#817) -- Update statictime tests following changes to p.a.disucssion (see - https://github.com/plone/plone.app.discussion/pull/204) - [instification] (#1520) -- Update @portrait endpoint to use sanitized user id [instification] (#1524) +- Updated ``statictime`` tests following changes to ``p.a.discussion`` (see + https://github.com/plone/plone.app.discussion/pull/204). @instification (#1520) +- Updated ``@portrait`` endpoint to use sanitized user id. @instification (#1524) 8.31.0 (2022-10-20) diff --git a/base.cfg b/base.cfg index a1b05f41b7..371b9fa4a7 100644 --- a/base.cfg +++ b/base.cfg @@ -27,7 +27,6 @@ allow-hosts = files.pythonhosted.org launchpad.net code.google.com - robotframework.googlecode.com [versions] # Do not use a release of plone.restapi: @@ -42,7 +41,6 @@ debug-exceptions = on eggs = Plone Pillow - plone.app.debugtoolbar plone.restapi [test] environment-vars = zope_i18n_compile_mo_files true diff --git a/news/1.bugfix b/news/1.bugfix deleted file mode 100644 index 1494e057ff..0000000000 --- a/news/1.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Replace deprecated assert methods. -[gforcada] diff --git a/news/1703.internal b/news/1703.internal deleted file mode 100644 index 6d5ce45b01..0000000000 --- a/news/1703.internal +++ /dev/null @@ -1 +0,0 @@ -Remove unused code. @davisagli diff --git a/news/1718.documentation b/news/1718.documentation deleted file mode 100644 index acca3051ee..0000000000 --- a/news/1718.documentation +++ /dev/null @@ -1 +0,0 @@ -Fix redirect for https://json-schema.org/. @stevepiercy diff --git a/plone-5.2.x.cfg b/plone-5.2.x.cfg index 158b669f23..d2924baf42 100644 --- a/plone-5.2.x.cfg +++ b/plone-5.2.x.cfg @@ -7,4 +7,5 @@ extends = black = 22.3.0 # we need the newest plone.rest release -plone.rest = 3.0.1 \ No newline at end of file +plone.rest = 3.0.1 +plone.app.linkintegrity = 3.6.2 diff --git a/plone-6.0.x.cfg b/plone-6.0.x.cfg index b29740736f..a882e859f4 100644 --- a/plone-6.0.x.cfg +++ b/plone-6.0.x.cfg @@ -15,3 +15,9 @@ zodb-temporary-storage = off [versions] black = 22.3.0 pygments = 2.14.0 +plone.app.linkintegrity = 4.0.3 +robotframework-browser = 17.5.2 +robotframework-assertion-engine = 2.0.0 +robotframework-debuglibrary = 2.3.0 +robotframework-pythonlibcore = 4.2.0 +grpcio-tools = 1.59.0 diff --git a/setup.py b/setup.py index b4351a8074..c5d7f0fc68 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import sys -version = "9.0.1.dev0" +version = "9.1.1.dev0" if sys.version_info.major == 2: raise ValueError( @@ -42,9 +42,8 @@ def read(filename): TEST_REQUIRES = [ "collective.MockMailHost", "plone.app.caching", - "plone.app.contenttypes", - "plone.app.robotframework", - "plone.app.testing [robot] >= 4.2.2", # ROBOT_TEST_LEVEL added + "plone.app.contenttypes[test]", + "plone.app.testing", "plone.api", "requests", "mock", @@ -71,6 +70,7 @@ def read(filename): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", ], diff --git a/src/plone/restapi/services/linkintegrity/get.py b/src/plone/restapi/services/linkintegrity/get.py index 6c6aa7ea05..32a5ecba7d 100644 --- a/src/plone/restapi/services/linkintegrity/get.py +++ b/src/plone/restapi/services/linkintegrity/get.py @@ -38,6 +38,9 @@ def reply(self): data = getMultiAdapter((item, self.request), ISerializeToJsonSummary)() data["breaches"] = [] for breach in breaches: + if breach["target"]["uid"] not in uids: + uids.append(breach["target"]["uid"]) + continue for source in breach.get("sources", []): # remove unwanted data source["@id"] = source["url"] diff --git a/src/plone/restapi/services/multilingual/configure.zcml b/src/plone/restapi/services/multilingual/configure.zcml index 58d65c74d3..8eec6946c0 100644 --- a/src/plone/restapi/services/multilingual/configure.zcml +++ b/src/plone/restapi/services/multilingual/configure.zcml @@ -20,6 +20,7 @@ factory=".pam.TranslationInfo" for="Products.CMFCore.interfaces.IContentish" permission="zope2.View" + layer="plone.app.multilingual.interfaces.IPloneAppMultilingualInstalled" name="@translations" /> @@ -41,6 +43,7 @@ factory=".pam.UnlinkTranslations" for="Products.CMFCore.interfaces.IContentish" permission="plone.app.multilingual.ManageTranslations" + layer="plone.app.multilingual.interfaces.IPloneAppMultilingualInstalled" name="@translations" /> @@ -49,6 +52,7 @@ factory=".locator.TranslationLocator" for="Products.CMFCore.interfaces.IContentish" permission="plone.app.multilingual.ManageTranslations" + layer="plone.app.multilingual.interfaces.IPloneAppMultilingualInstalled" name="@translation-locator" /> diff --git a/src/plone/restapi/tests/robot/test.robot b/src/plone/restapi/tests/robot/test.robot deleted file mode 100644 index 9313a7d5d1..0000000000 --- a/src/plone/restapi/tests/robot/test.robot +++ /dev/null @@ -1,22 +0,0 @@ -*** Settings *** - -Library Selenium2Library timeout=10 implicit_wait=0.5 - -Suite Setup Start browser -Suite Teardown Close All Browsers - -*** Variables *** - -${BROWSER} = firefox - -*** Test Cases *** - -Plone site - [Tags] start - Go to ${PLONE_URL} - Page should contain Plone site - -*** Keywords *** - -Start browser - Open browser ${PLONE_URL} browser=${BROWSER} diff --git a/src/plone/restapi/tests/test_robot.py.txt b/src/plone/restapi/tests/test_robot.py.txt deleted file mode 100644 index ba16dbdef9..0000000000 --- a/src/plone/restapi/tests/test_robot.py.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from plone.app.testing import ROBOT_TEST_LEVEL -from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING -from plone.testing import layered -import robotsuite -import unittest -import os - - -def test_suite(): - suite = unittest.TestSuite() - current_dir = os.path.abspath(os.path.dirname(__file__)) - robot_dir = os.path.join(current_dir, 'robot') - robot_tests = [ - os.path.join('robot', doc) for doc in - os.listdir(robot_dir) if doc.endswith('.robot') and - doc.startswith('test_') - ] - for robot_test in robot_tests: - robottestsuite = robotsuite.RobotTestSuite(robot_test) - robottestsuite.level = ROBOT_TEST_LEVEL - suite.addTests([ - layered( - robottestsuite, - layer=PLONE_RESTAPI_DX_FUNCTIONAL_TESTING - ), - ]) - return suite diff --git a/src/plone/restapi/tests/test_services_linkintegrity.py b/src/plone/restapi/tests/test_services_linkintegrity.py index 297843f709..9492796eb5 100644 --- a/src/plone/restapi/tests/test_services_linkintegrity.py +++ b/src/plone/restapi/tests/test_services_linkintegrity.py @@ -20,7 +20,6 @@ class TestLinkIntegrity(unittest.TestCase): - layer = PLONE_RESTAPI_BLOCKS_FUNCTIONAL_TESTING def setUp(self): @@ -210,3 +209,164 @@ def test_return_items_total_in_subfolders(self): self.assertEqual(result[0]["@id"], level1.absolute_url()) self.assertEqual(result[0]["breaches"], []) self.assertEqual(result[0]["items_total"], 1) + + def test_tree_breaches_no_duplicates(self): + # /target_parent/target_child + target_parent = createContentInContainer( + self.portal, "Folder", id="target-parent" + ) + target_child = createContentInContainer( + target_parent, "Document", id="target-child" + ) + target_parent_uid = IUUID(target_parent) + target_child_uid = IUUID(target_child) + + source_a = createContentInContainer( + self.portal, + "Document", + id="source-a", + title="Source A", + blocks={ + "block-uuid1": { + "@type": "text", + "text": { + "blocks": [{"text": "some link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_parent_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_parent_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + "block-uuid2": { + "@type": "text", + "text": { + "blocks": [{"text": "some other link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_child_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_child_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + }, + ) + + source_b = createContentInContainer( + self.portal, + "Document", + id="source-b", + title="Source B", + blocks={ + "block-uuid3": { + "@type": "text", + "text": { + "blocks": [{"text": "some link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_parent_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_parent_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + } + }, + ) + + source_c = createContentInContainer( + self.portal, + "Document", + id="source-c", + title="Source C", + blocks={ + "block-uuid4": { + "@type": "text", + "text": { + "blocks": [{"text": "some other link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_child_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_child_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + }, + ) + + transaction.commit() + + response = self.api_session.get( + "/@linkintegrity", params={"uids": [target_parent_uid]} + ) + + results = response.json() + self.assertEqual( + [ + { + "@id": target_parent.absolute_url(), + "@type": "Folder", + "breaches": [ + { + "@id": source_a.absolute_url(), + "title": "Source A", + "uid": IUUID(source_a), + }, + { + "@id": source_b.absolute_url(), + "title": "Source B", + "uid": IUUID(source_b), + }, + ], + "description": "", + "items_total": 1, + "review_state": "private", + "title": "", + "type_title": "Folder", + }, + { + "@id": target_child.absolute_url(), + "@type": "Document", + "breaches": [ + { + "@id": source_a.absolute_url(), + "title": "Source A", + "uid": IUUID(source_a), + }, + { + "@id": source_c.absolute_url(), + "title": "Source C", + "uid": IUUID(source_c), + }, + ], + "description": "", + "items_total": 0, + "review_state": "private", + "title": "", + "type_title": "Page", + }, + ], + results, + ) diff --git a/src/plone/restapi/tests/test_translations.py b/src/plone/restapi/tests/test_translations.py index c32f72eff8..a8fa3bdd61 100644 --- a/src/plone/restapi/tests/test_translations.py +++ b/src/plone/restapi/tests/test_translations.py @@ -9,6 +9,7 @@ from plone.restapi.bbb import ILanguage from plone.restapi.testing import PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING from plone.restapi.testing import PLONE_RESTAPI_DX_PAM_INTEGRATION_TESTING +from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING from zope.component import getMultiAdapter from zope.interface import alsoProvides @@ -374,3 +375,30 @@ def test_translation_locator(self): self.assertEqual(200, response.status_code) self.assertEqual(self.portal_url + "/de", response.json().get("@id")) + + +class TestPAMNotinstalled(unittest.TestCase): + layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING + + def setUp(self): + self.portal = self.layer["portal"] + self.request = self.layer["request"] + login(self.portal, SITE_OWNER_NAME) + self.folder = createContentInContainer(self.portal, "Folder", title="Folder") + transaction.commit() + + def test_translations(self): + response = requests.get( + f"{self.folder.absolute_url()}/@translations", + headers={"Accept": "application/json"}, + auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), + ) + self.assertEqual(404, response.status_code) + + def test_translation_locator(self): + response = requests.get( + f"{self.folder.absolute_url()}/@translation-locator", + headers={"Accept": "application/json"}, + auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), + ) + self.assertEqual(404, response.status_code)