diff --git a/README.md b/README.md index f262737..9ce6994 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The export configuration items may contain the following keys: | notion_filter | A [notion filter object](https://developers.notion.com/reference/post-database-query-filter) to be applied to the database. | | notion_sorts | A [notion sorts object](https://developers.notion.com/reference/post-database-query-sort) to be applied to the database. | | property_map | A mapping between the name of properties in Notion, and the name of the properties in the exported files. Set the new value to `null` to discard the property.| -| keep_unmapped_properties | Defaults to `true`. When set to `false`, the `property_map` acts as an allowlist: any property not in the map is dropped from the export. This prevents properties added or renamed in Notion from leaking into the exported files. The `id_property` and `url_property` are always kept. | +| keep_unmapped_properties | Defaults to `true`. When set to `false`, the `property_map` acts as an allowlist: any Notion property not in the map is dropped from the export. This prevents properties added or renamed in Notion from leaking into the exported files. The `id_property`, the `url_property`, and properties injected by plugins are always kept. | ## Example Configuration Files diff --git a/n2y/export.py b/n2y/export.py index b8e0c6a..c4865e8 100644 --- a/n2y/export.py +++ b/n2y/export.py @@ -54,13 +54,16 @@ def _page_properties( page.client.logger.warning(msg, original, page.notion_url, original, new) if not keep_unmapped_properties: # Treat the property map as an allowlist so that properties added or - # renamed in Notion don't leak into the exported files. + # renamed in Notion don't leak into the exported files. Only properties + # that exist in Notion are dropped; properties injected by plugins + # (which won't appear in the page's notion_data) are always kept. + notion_property_names = set(page.notion_data["properties"].keys()) kept_properties = {new for new in property_map.values() if new} kept_properties.update(p for p in (id_property, url_property) if p) properties = { name: value for name, value in properties.items() - if name in kept_properties + if name in kept_properties or name not in notion_property_names } return properties diff --git a/tests/test_audit_end_to_end.py b/tests/test_audit_end_to_end.py index 4f2b262..6e0e141 100644 --- a/tests/test_audit_end_to_end.py +++ b/tests/test_audit_end_to_end.py @@ -25,19 +25,22 @@ def test_audit(): status, stdoutput = run_n2yaudit([object_id]) assert status == 3 - external_mention_in_top_page = "https://www.notion.so/Audited-cfa8ff07bba244c8b967c9b6a7a954c1#aa4fa886f8244c818de8018bb3491806" # noqa: E501 - external_mention_in_child_page = "https://www.notion.so/Child-f3e3628fc80c470ea68994fa7ec0ff17#d1d32ff6f0cb4c71a2f1c4ec55e00086" # noqa: E501 - internal_mention_in_child_page = "https://www.notion.so/Child-f3e3628fc80c470ea68994fa7ec0ff17#eab91ccc32924221ac3f0a74225a33dd" # noqa: E501 - external_mention_in_child_database = "https://www.notion.so/B-4412005dcec24ff2827abbc367c90b29#6373a0b5c2804fbe9dfac167ce6948a0" # noqa: E501 - internal_mention_in_database_in_column = "https://www.notion.so/Audited-cfa8ff07bba244c8b967c9b6a7a954c1#21a13c06ef86462e882a181c6cb52a64" # noqa: E501 + # Only the page-slug#block-id fragment is asserted (not the full URL) + # because Notion has changed the URL host/path over time (e.g. from + # www.notion.so/ to app.notion.com/p/). + external_mention_in_top_page = "Audited-cfa8ff07bba244c8b967c9b6a7a954c1#aa4fa886f8244c818de8018bb3491806" # noqa: E501 + external_mention_in_child_page = "Child-f3e3628fc80c470ea68994fa7ec0ff17#d1d32ff6f0cb4c71a2f1c4ec55e00086" # noqa: E501 + internal_mention_in_child_page = "Child-f3e3628fc80c470ea68994fa7ec0ff17#eab91ccc32924221ac3f0a74225a33dd" # noqa: E501 + external_mention_in_child_database = "B-4412005dcec24ff2827abbc367c90b29#6373a0b5c2804fbe9dfac167ce6948a0" # noqa: E501 + internal_mention_in_database_in_column = "Audited-cfa8ff07bba244c8b967c9b6a7a954c1#21a13c06ef86462e882a181c6cb52a64" # noqa: E501 # NOTE: When you try to get a link for a "LinkToPageBlock" in the Notion UI, # it appears to give you the URL for the linked page or database, and not to # the block itself. Thus, these two links were extracted using a debugger # when running this test. - external_link_in_top_page = "https://www.notion.so/Audited-cfa8ff07bba244c8b967c9b6a7a954c1#c22d76d50c704761b0e729531e6cc24b" # noqa: E501 - internal_link_in_top_page = "https://www.notion.so/Audited-cfa8ff07bba244c8b967c9b6a7a954c1#3e2e2fb2e9bf4a1f8b00bbb18a9d97e9" # noqa: E501 + external_link_in_top_page = "Audited-cfa8ff07bba244c8b967c9b6a7a954c1#c22d76d50c704761b0e729531e6cc24b" # noqa: E501 + internal_link_in_top_page = "Audited-cfa8ff07bba244c8b967c9b6a7a954c1#3e2e2fb2e9bf4a1f8b00bbb18a9d97e9" # noqa: E501 assert external_mention_in_top_page in stdoutput assert external_mention_in_child_page in stdoutput diff --git a/tests/test_export.py b/tests/test_export.py index 3dad279..93ed11e 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -56,6 +56,16 @@ def test_page_properties_drop_unmapped_keeps_id_and_url(page): assert set(properties) == {"id", "url"} +def test_page_properties_drop_unmapped_keeps_plugin_injected(page): + # Plugins may inject extra properties (e.g. embedded mention data) that + # don't exist in the page's notion_data; the allowlist must keep them. + page.properties["injected"] = page.properties["property"] + properties = _page_properties( + page, property_map={"property": "p"}, keep_unmapped_properties=False + ) + assert properties == {"p": "P\n", "injected": "P\n"} + + def test_page_properties_drop_unmapped_keeps_mapped_title(page): properties = _page_properties( page, property_map={"title": "title"}, keep_unmapped_properties=False