diff --git a/app/templates/product_detail.html b/app/templates/product_detail.html index a569d2d..a6abc55 100644 --- a/app/templates/product_detail.html +++ b/app/templates/product_detail.html @@ -282,7 +282,8 @@

Combined product view

{% else %}

- This product does not currently have eligible product/vendor synonym or equivalent links. + This product does not currently have eligible product or vendor relationships for a combined view + ({{ combined_view_relationship_types|join(", ") }}).

{% endif %} diff --git a/app/templates/vendor_detail.html b/app/templates/vendor_detail.html index 3ee38f7..2338f7d 100644 --- a/app/templates/vendor_detail.html +++ b/app/templates/vendor_detail.html @@ -264,7 +264,8 @@

Combined products view

{% else %}

- This vendor does not currently have eligible synonym/equivalent vendor links for combined products. + This vendor does not currently have eligible vendor relationships for combined products + ({{ combined_view_relationship_types|join(", ") }}).

{% endif %} diff --git a/app/views.py b/app/views.py index 77790ee..a081061 100644 --- a/app/views.py +++ b/app/views.py @@ -178,6 +178,7 @@ def _limited_page(query, page, per_page): "synonym-of", "canonical-of", "equivalent-to", + "renamed-to", } ALLOWED_PROPOSAL_TYPES = { "edit_cpe", diff --git a/tests/test_combined_views.py b/tests/test_combined_views.py new file mode 100644 index 0000000..fc061c8 --- /dev/null +++ b/tests/test_combined_views.py @@ -0,0 +1,70 @@ +import pytest + +from app.models import CPEEntry, EntityRelationship, Product, Vendor, db + + +@pytest.mark.parametrize("entity_type", ["vendor", "product"]) +def test_rename_chains_are_combined_from_either_end(client, app, entity_type): + vendors = [Vendor(name=f"rename_vendor_{index}") for index in range(3)] + db.session.add_all(vendors) + db.session.flush() + products = [ + Product( + vendor=vendor, + name="same_token" if entity_type == "vendor" else f"rename_product_{index}", + ) + for index, vendor in enumerate(vendors) + ] + db.session.add_all(products) + db.session.flush() + cpes = [ + CPEEntry( + vendor=product.vendor, + product=product, + cpe_uri=f"cpe:2.3:a:{product.vendor.name}:{product.name}:1:*:*:*:*:*:*:*", + part="a", + version="1", + ) + for product in products + ] + records = vendors if entity_type == "vendor" else products + for source, target in zip(records, records[1:]): + db.session.add(EntityRelationship( + **{f"source_{entity_type}": source, f"target_{entity_type}": target}, + relationship_type="renamed-to", + )) + # Other products under renamed vendors belong only in the vendor combined view. + other_product = Product(vendor=vendors[1], name="unrelated_token") + db.session.add_all(cpes + [other_product]) + db.session.commit() + + for index in (0, 2): + if entity_type == "vendor": + response = client.get(f"/vendors/{vendors[index].uuid}") + assert response.status_code == 200 + combined = response.get_data(as_text=True).split( + "

Combined products view

", 1 + )[1] + for product in [*products, other_product]: + assert product.name in combined + if product.vendor_id != vendors[index].id: + assert f'href="/products/{product.uuid}"' in combined + assert "renamed-to" in combined + + response = client.get(f"/products/{products[index].uuid}") + assert response.status_code == 200 + combined = response.get_data(as_text=True).split( + "

Combined product view

", 1 + )[1].split("

PURL mappings

", 1)[0] + assert "renamed-to" in combined + for cpe in cpes: + assert cpe.cpe_uri in combined + assert other_product.name not in combined + + graph = client.get(f"/products/{products[index].uuid}/graph") + assert graph.status_code == 200 + combined_product_ids = { + node["id"] for node in graph.get_json()["nodes"] + if node["data"]["kind"] == "product" and node["data"]["combined"] + } + assert combined_product_ids == {f"product:{product.uuid}" for product in products}