diff --git a/assets/src/bundles/browse/swhid-utils.js b/assets/src/bundles/browse/swhid-utils.js --- a/assets/src/bundles/browse/swhid-utils.js +++ b/assets/src/bundles/browse/swhid-utils.js @@ -112,7 +112,7 @@ $('#swh-identifiers').tabSlideOut(tabSlideOptions); // set the tab visible once the close animation is terminated - $('#swh-identifiers').css('display', 'block'); + $('#swh-identifiers').addClass('d-none d-sm-block'); $('.swhid-context-option').trigger('click'); // highlighted code lines changed diff --git a/assets/src/bundles/webapp/code-highlighting.js b/assets/src/bundles/webapp/code-highlighting.js --- a/assets/src/bundles/webapp/code-highlighting.js +++ b/assets/src/bundles/webapp/code-highlighting.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2018-2019 The Software Heritage developers + * Copyright (C) 2018-2021 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information @@ -22,16 +22,29 @@ return lineTd; } +// function to highlight a range of lines +export function highlightLines(first, last) { + if (!first) { + return; + } + if (!last) { + last = first; + } + for (let i = first; i <= last; ++i) { + highlightLine(i); + } +} + // function to reset highlighting export function resetHighlightedLines() { firstHighlightedLine = null; $('.hljs-ln-line[data-line-number]').css('background-color', 'inherit'); } -export function scrollToLine(lineDomElt) { +export function scrollToLine(lineDomElt, offset = 70) { if ($(lineDomElt).closest('.swh-content').length > 0) { $('html, body').animate({ - scrollTop: $(lineDomElt).offset().top - 70 + scrollTop: $(lineDomElt).offset().top - offset }, 500); } } @@ -60,9 +73,7 @@ } else if (lines[0] < lines[lines.length - 1]) { firstHighlightedLine = parseInt(lines[0]); scrollToLine(highlightLine(lines[0])); - for (let i = lines[0] + 1; i <= lines[lines.length - 1]; ++i) { - highlightLine(i); - } + highlightLines(lines[0] + 1, lines[lines.length - 1]); } } @@ -88,9 +99,7 @@ if (evt.shiftKey && firstHighlightedLine && line > firstHighlightedLine) { const firstLine = firstHighlightedLine; resetHighlightedLines(); - for (let i = firstLine; i <= line; ++i) { - highlightLine(i); - } + highlightLines(firstLine, line); firstHighlightedLine = firstLine; window.location.hash = `#L${firstLine}-L${line}`; } else { @@ -115,3 +124,7 @@ }); } + +export function disableCodeSelection() { + $('.swh-content').unbind(); +} diff --git a/assets/src/bundles/webapp/iframes.js b/assets/src/bundles/webapp/iframes.js new file mode 100644 --- /dev/null +++ b/assets/src/bundles/webapp/iframes.js @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2021 The Software Heritage developers + * See the AUTHORS file at the top-level directory of this distribution + * License: GNU Affero General Public License version 3, or any later version + * See top-level LICENSE file for more information + */ + +export function showIframeInfoModal(objectType, objectSWHID) { + const html = ` +

+ You can embed that ${objectType} view in an external website + through the use of an iframe. Use the following HTML code + to do so. +

+
<iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+        src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}">
+</iframe>
+ `; + swh.webapp.showModalHtml(`Software Heritage ${objectType} iframe`, html, '1000px'); + swh.webapp.highlightCode(false, '.swh-iframe-html'); +} diff --git a/assets/src/bundles/webapp/index.js b/assets/src/bundles/webapp/index.js --- a/assets/src/bundles/webapp/index.js +++ b/assets/src/bundles/webapp/index.js @@ -26,3 +26,4 @@ export * from './sentry'; export * from './math-typesetting'; export * from './status-widget'; +export * from './iframes'; diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js --- a/assets/src/bundles/webapp/webapp-utils.js +++ b/assets/src/bundles/webapp/webapp-utils.js @@ -251,9 +251,11 @@ $('#swh-web-modal-confirm').modal('show'); } -export function showModalHtml(title, html) { +export function showModalHtml(title, html, width = '500px') { $('#swh-web-modal-html .modal-title').text(title); $('#swh-web-modal-html .modal-body').html(html); + $('#swh-web-modal-html .modal-dialog').css('max-width', width); + $('#swh-web-modal-html .modal-dialog').css('width', width); $('#swh-web-modal-html').modal('show'); } diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css --- a/assets/src/bundles/webapp/webapp.css +++ b/assets/src/bundles/webapp/webapp.css @@ -662,6 +662,7 @@ } .swh-badge-html, +.swh-iframe-html, .swh-badge-md, .swh-badge-rst { white-space: pre-wrap !important; diff --git a/docs/index.rst b/docs/index.rst --- a/docs/index.rst +++ b/docs/index.rst @@ -15,6 +15,7 @@ uri-scheme-api uri-scheme-browse uri-scheme-identifiers + uri-scheme-misc diff --git a/docs/uri-scheme-misc.rst b/docs/uri-scheme-misc.rst new file mode 100644 --- /dev/null +++ b/docs/uri-scheme-misc.rst @@ -0,0 +1,41 @@ +Miscellaneous URLs +^^^^^^^^^^^^^^^^^^ + +Iframe view for contents and directories +---------------------------------------- + +A subset of Software Heritage objects (contents and directories) can be embedded +in external websites through the use of iframes. A dedicated endpoint serving +a minimalist Web UI is available for that use case. + +.. http:get:: /embed/(swhid)/ + + Endpoint to embed Software Heritage objects in external websites using + an iframe. + + :param string swhid: a SoftWare Heritage persistent IDentifier + object, or SWHID (see :ref:`persistent-identifiers` to learn more about its syntax) + + :statuscode 200: no error + :statuscode 400: the provided identifier is malformed or + the object type is not supported by the view + :statuscode 404: requested object cannot be found in the archive + + **Example:** + + By adding HTML code similar to the one below in a web page, + + .. code-block:: html + + + + you will obtain the following rendering. + + .. raw:: html + + + diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py --- a/swh/web/common/utils.py +++ b/swh/web/common/utils.py @@ -285,6 +285,7 @@ ), "swh_web_version": get_distribution("swh.web").version, "visit_types": ORIGIN_VISIT_TYPES, + "iframe_mode": False, } diff --git a/swh/web/misc/iframe.py b/swh/web/misc/iframe.py new file mode 100644 --- /dev/null +++ b/swh/web/misc/iframe.py @@ -0,0 +1,310 @@ +# Copyright (C) 2021 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Any, Dict, List, Optional + +from django.conf.urls import url +from django.shortcuts import render +from django.views.decorators.clickjacking import xframe_options_exempt + +from swh.model.hashutil import hash_to_bytes +from swh.model.identifiers import ( + CONTENT, + DIRECTORY, + REVISION, + SNAPSHOT, + ObjectType, + QualifiedSWHID, +) +from swh.web.browse.snapshot_context import get_snapshot_context +from swh.web.browse.utils import ( + content_display_max_size, + get_directory_entries, + prepare_content_for_display, + request_content, +) +from swh.web.common import archive +from swh.web.common.exc import BadInputExc, NotFoundExc, http_status_code_message +from swh.web.common.identifiers import get_swhid, get_swhids_info +from swh.web.common.typing import SnapshotContext, SWHObjectInfo +from swh.web.common.utils import gen_path_info, reverse + + +def _get_content_rendering_data(cnt_swhid: QualifiedSWHID, path: str) -> Dict[str, Any]: + content_data = request_content(f"sha1_git:{cnt_swhid.object_id.hex()}") + content = None + language = None + mimetype = None + if content_data.get("raw_data") is not None: + content_display_data = prepare_content_for_display( + content_data["raw_data"], content_data["mimetype"], path + ) + content = content_display_data["content_data"] + language = content_display_data["language"] + mimetype = content_display_data["mimetype"] + + return { + "content": content, + "content_size": content_data.get("length"), + "max_content_size": content_display_max_size, + "filename": path.split("/")[-1], + "encoding": content_data.get("encoding"), + "mimetype": mimetype, + "language": language, + } + + +def _get_directory_rendering_data( + dir_swhid: QualifiedSWHID, focus_swhid: QualifiedSWHID, path: str, +) -> Dict[str, Any]: + dirs, files = get_directory_entries(dir_swhid.object_id.hex()) + for d in dirs: + if d["type"] == "rev": + d["url"] = None + else: + dir_swhid = QualifiedSWHID( + object_type=ObjectType.DIRECTORY, + object_id=hash_to_bytes(d["target"]), + origin=dir_swhid.origin, + visit=dir_swhid.visit, + anchor=dir_swhid.anchor, + path=(path or "/") + d["name"] + "/", + ) + d["url"] = reverse( + "swhid-iframe", + url_args={"swhid": str(dir_swhid)}, + query_params={"focus_swhid": str(focus_swhid)}, + ) + + for f in files: + object_id = hash_to_bytes(f["target"]) + cnt_swhid = QualifiedSWHID( + object_type=ObjectType.CONTENT, + object_id=object_id, + origin=dir_swhid.origin, + visit=dir_swhid.visit, + anchor=dir_swhid.anchor, + path=(path or "/") + f["name"], + lines=(focus_swhid.lines if object_id == focus_swhid.object_id else None), + ) + f["url"] = reverse( + "swhid-iframe", + url_args={"swhid": str(cnt_swhid)}, + query_params={"focus_swhid": str(focus_swhid)}, + ) + + return {"dirs": dirs, "files": files} + + +def _get_breacrumbs_data( + swhid: QualifiedSWHID, + focus_swhid: QualifiedSWHID, + path: str, + snapshot_context: Optional[SnapshotContext] = None, +) -> List[Dict[str, Any]]: + breadcrumbs = [] + filename = None + # strip any leading or trailing slash from path qualifier of SWHID + if path and path[0] == "/": + path = path[1:] + if path and path[-1] == "/": + path = path[:-1] + if swhid.object_type == ObjectType.CONTENT: + split_path = path.split("/") + filename = split_path[-1] + path = path[: -len(filename)] + + path_info = gen_path_info(path) if path != "/" else [] + + root_dir = None + if snapshot_context and snapshot_context["root_directory"]: + root_dir = snapshot_context["root_directory"] + elif focus_swhid.object_type == ObjectType.DIRECTORY: + root_dir = focus_swhid.object_id.hex() + + if root_dir: + root_dir_swhid = QualifiedSWHID( + object_type=ObjectType.DIRECTORY, + object_id=hash_to_bytes(root_dir), + origin=swhid.origin, + visit=swhid.visit, + anchor=swhid.anchor, + ) + breadcrumbs.append( + { + "name": root_dir[:7], + "object_id": root_dir_swhid.object_id.hex(), + "path": "/", + "url": reverse( + "swhid-iframe", + url_args={"swhid": str(root_dir_swhid)}, + query_params={"focus_swhid": focus_swhid}, + ), + } + ) + + for pi in path_info: + dir_info = archive.lookup_directory_with_path(root_dir, pi["path"]) + dir_swhid = QualifiedSWHID( + object_type=ObjectType.DIRECTORY, + object_id=hash_to_bytes(dir_info["target"]), + origin=swhid.origin, + visit=swhid.visit, + anchor=swhid.anchor, + path="/" + pi["path"] + "/", + ) + breadcrumbs.append( + { + "name": pi["name"], + "object_id": dir_swhid.object_id.hex(), + "path": dir_swhid.path.decode("utf-8") if dir_swhid.path else "", + "url": reverse( + "swhid-iframe", + url_args={"swhid": str(dir_swhid)}, + query_params={"focus_swhid": focus_swhid}, + ), + } + ) + if filename: + breadcrumbs.append( + { + "name": filename, + "object_id": swhid.object_id.hex(), + "path": path, + "url": "", + } + ) + + return breadcrumbs + + +@xframe_options_exempt +def swhid_iframe(request, swhid: str): + """Django view that can be embedded in an iframe to display objects archived + by Software Heritage (currently contents and directories) in a minimalist + Web UI. + """ + focus_swhid = request.GET.get("focus_swhid", swhid) + parsed_swhid = None + view_data = {} + breadcrumbs = [] + swh_objects = [] + snapshot_context = None + swhids_info_extra_context = {} + try: + parsed_swhid = get_swhid(swhid) + parsed_focus_swhid = get_swhid(focus_swhid) + path = parsed_swhid.path.decode("utf-8") if parsed_swhid.path else "" + + snapshot_context = None + revision_id = None + if ( + parsed_swhid.anchor + and parsed_swhid.anchor.object_type == ObjectType.REVISION + ): + revision_id = parsed_swhid.anchor.object_id.hex() + if parsed_swhid.origin or parsed_swhid.visit: + snapshot_context = get_snapshot_context( + origin_url=parsed_swhid.origin, + snapshot_id=parsed_swhid.visit.object_id.hex() + if parsed_swhid.visit + else None, + revision_id=revision_id, + ) + + error_info: Dict[str, Any] = {"status_code": 200, "description": ""} + + if parsed_swhid and parsed_swhid.object_type == ObjectType.CONTENT: + view_data = _get_content_rendering_data(parsed_swhid, path) + swh_objects.append( + SWHObjectInfo( + object_type=CONTENT, object_id=parsed_swhid.object_id.hex() + ) + ) + + elif parsed_swhid and parsed_swhid.object_type == ObjectType.DIRECTORY: + view_data = _get_directory_rendering_data( + parsed_swhid, parsed_focus_swhid, path + ) + swh_objects.append( + SWHObjectInfo( + object_type=DIRECTORY, object_id=parsed_swhid.object_id.hex() + ) + ) + + elif parsed_swhid: + error_info = { + "status_code": 400, + "description": ( + f"Objects of type {parsed_swhid.object_type} are not supported" + ), + } + + swhids_info_extra_context["path"] = path + if parsed_swhid and view_data: + breadcrumbs = _get_breacrumbs_data( + parsed_swhid, parsed_focus_swhid, path, snapshot_context + ) + + if parsed_swhid.object_type == ObjectType.CONTENT and len(breadcrumbs) > 1: + swh_objects.append( + SWHObjectInfo( + object_type=DIRECTORY, object_id=breadcrumbs[-2]["object_id"] + ) + ) + swhids_info_extra_context["path"] = breadcrumbs[-2]["path"] + swhids_info_extra_context["filename"] = breadcrumbs[-1]["name"] + + if snapshot_context: + swh_objects.append( + SWHObjectInfo( + object_type=REVISION, + object_id=snapshot_context["revision_id"] or "", + ) + ) + swh_objects.append( + SWHObjectInfo( + object_type=SNAPSHOT, + object_id=snapshot_context["snapshot_id"] or "", + ) + ) + + except BadInputExc as e: + error_info = {"status_code": 400, "description": f"BadInputExc: {str(e)}"} + except NotFoundExc as e: + error_info = {"status_code": 404, "description": f"NotFoundExc: {str(e)}"} + except Exception as e: + error_info = {"status_code": 500, "description": str(e)} + + return render( + request, + "misc/iframe.html", + { + **view_data, + "iframe_mode": True, + "object_type": parsed_swhid.object_type.value if parsed_swhid else None, + "lines": parsed_swhid.lines if parsed_swhid else None, + "breadcrumbs": breadcrumbs, + "swhid": swhid, + "focus_swhid": focus_swhid, + "error_code": error_info["status_code"], + "error_message": http_status_code_message.get(error_info["status_code"]), + "error_description": error_info["description"], + "snapshot_context": None, + "swhids_info": get_swhids_info( + swh_objects, snapshot_context, swhids_info_extra_context + ), + }, + status=error_info["status_code"], + ) + + +urlpatterns = [ + url( + r"^embed/(?Pswh:[0-9]+:[a-z]+:[0-9a-f]+.*)$", + swhid_iframe, + name="swhid-iframe", + ), +] diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py --- a/swh/web/misc/urls.py +++ b/swh/web/misc/urls.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019 The Software Heritage developers +# Copyright (C) 2019-2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU Affero General Public License version 3, or any later version # See top-level LICENSE file for more information @@ -51,6 +51,7 @@ url(r"^stat_counters/", _stat_counters, name="stat-counters"), url(r"^", include("swh.web.misc.badges")), url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"), + url(r"^", include("swh.web.misc.iframe")), ] diff --git a/swh/web/templates/includes/breadcrumbs.html b/swh/web/templates/includes/breadcrumbs.html --- a/swh/web/templates/includes/breadcrumbs.html +++ b/swh/web/templates/includes/breadcrumbs.html @@ -1,24 +1,23 @@ {% comment %} -Copyright (C) 2017-2018 The Software Heritage developers +Copyright (C) 2017-2021 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information {% endcomment %} {% if breadcrumbs %} - {% if breadcrumbs|length > 1 or breadcrumbs.0.url %} - - {% endif %} + {% if iframe_mode or breadcrumbs|length > 1 or breadcrumbs.0.url %} + + {% endif %} {% endif %} - diff --git a/swh/web/templates/includes/content-display.html b/swh/web/templates/includes/content-display.html --- a/swh/web/templates/includes/content-display.html +++ b/swh/web/templates/includes/content-display.html @@ -1,5 +1,5 @@ {% comment %} -Copyright (C) 2017-2020 The Software Heritage developers +Copyright (C) 2017-2021 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information @@ -12,12 +12,14 @@ {% if snapshot_context and snapshot_context.is_empty %} {% include "includes/empty-snapshot.html" %} {% else %} -
- {% if filename %} -
- {{ filename }} -
- {% endif %} + {% if not iframe_mode %} +
+ {% if filename %} +
+ {{ filename }} +
+ {% endif %} + {% endif %}
{% if content_size > max_content_size %} Content is too large to be displayed (size is greater than {{ max_content_size|filesizeformat }}). @@ -47,7 +49,9 @@ {% include "includes/http-error.html" %} {% endif %}
-
+ {% if not iframe_mode %} +
+ {% endif %} {% if content %} + {% render_bundle 'vendors' %} + {% render_bundle 'webapp' %} + {% render_bundle 'browse' %} + + + + + +
+
+
+ {% include "includes/show-swhids.html" %} +
+
+
+ +
+ + Software + Heritage +
+
+
+ Navigating in + +
+
+ {% if swhid != focus_swhid %} + +
Reset view
+ +
+ {% endif %} + + View in the archive + + + + + +
+
+ +
+
+ {% if error_code != 200 %} + {% include "includes/http-error.html" %} + {% elif object_type == "cnt" %} + {% include "includes/content-display.html" %} + {% elif object_type == "dir" %} + {% include "includes/directory-display.html" %} + {% endif %} +
+
+
+
+
+ + JavaScript license information + + {% if object_type == "cnt" %} + + {% endif %} + + + diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py --- a/swh/web/tests/data.py +++ b/swh/web/tests/data.py @@ -20,6 +20,7 @@ from swh.indexer.storage.model import OriginIntrinsicMetadataRow from swh.loader.git.from_disk import GitLoaderFromArchive from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_hex +from swh.model.identifiers import CoreSWHID, ObjectType, QualifiedSWHID from swh.model.model import ( Content, Directory, @@ -277,18 +278,35 @@ revisions = set() releases = set() snapshots = set() + swhids = [] content_path = {} # Get all objects loaded into the test archive common_metadata = {ORIGIN_METADATA_KEY: ORIGIN_METADATA_VALUE} for origin in _TEST_ORIGINS: + origin_revisions = set() snp = snapshot_get_latest(storage, origin["url"]) + swhids.append( + QualifiedSWHID( + object_type=ObjectType.SNAPSHOT, object_id=snp.id, origin=origin["url"] + ) + ) snapshots.add(hash_to_hex(snp.id)) for branch_name, branch_data in snp.branches.items(): target_type = branch_data.target_type.value if target_type == "revision": - revisions.add(branch_data.target) + origin_revisions.add(branch_data.target) + swhids.append( + QualifiedSWHID( + object_type=ObjectType.REVISION, + object_id=branch_data.target, + origin=origin["url"], + visit=CoreSWHID( + object_type=ObjectType.SNAPSHOT, object_id=snp.id + ), + ) + ) if b"master" in branch_name: # Add some origin intrinsic metadata for tests metadata = common_metadata @@ -310,14 +328,24 @@ ) elif target_type == "release": release = storage.release_get([branch_data.target])[0] - revisions.add(release.target) + origin_revisions.add(release.target) releases.add(hash_to_hex(branch_data.target)) + swhids.append( + QualifiedSWHID( + object_type=ObjectType.RELEASE, + object_id=branch_data.target, + origin=origin["url"], + visit=CoreSWHID( + object_type=ObjectType.SNAPSHOT, object_id=snp.id + ), + ) + ) - for rev_log in storage.revision_shortlog(set(revisions)): + for rev_log in storage.revision_shortlog(origin_revisions): rev_id = rev_log[0] revisions.add(rev_id) - for rev in storage.revision_get(revisions): + for rev in storage.revision_get(origin_revisions): if rev is None: continue dir_id = rev.directory @@ -328,8 +356,36 @@ content_path[entry["sha1"]] = "/".join( [hash_to_hex(dir_id), entry["path"].decode("utf-8")] ) + swhids.append( + QualifiedSWHID( + object_type=ObjectType.CONTENT, + object_id=entry["sha1_git"], + origin=origin["url"], + visit=CoreSWHID( + object_type=ObjectType.SNAPSHOT, object_id=snp.id + ), + anchor=CoreSWHID( + object_type=ObjectType.REVISION, object_id=rev.id + ), + path=b"/" + entry["path"], + ) + ) elif entry["type"] == "dir": directories.add(hash_to_hex(entry["target"])) + swhids.append( + QualifiedSWHID( + object_type=ObjectType.DIRECTORY, + object_id=entry["target"], + origin=origin["url"], + visit=CoreSWHID( + object_type=ObjectType.SNAPSHOT, object_id=snp.id + ), + anchor=CoreSWHID( + object_type=ObjectType.REVISION, object_id=rev.id + ), + path=b"/" + entry["path"] + b"/", + ) + ) _add_extra_contents(storage, sha1s) @@ -413,7 +469,7 @@ "releases": list(releases), "revisions": list(map(hash_to_hex, revisions)), "snapshots": list(snapshots), - "generated_checksums": set(), + "swhids": swhids, } diff --git a/swh/web/tests/misc/test_iframe.py b/swh/web/tests/misc/test_iframe.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/misc/test_iframe.py @@ -0,0 +1,83 @@ +# Copyright (C) 2021 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from hypothesis import given + +from swh.model.hashutil import hash_to_bytes +from swh.model.identifiers import CoreSWHID, ObjectType +from swh.web.common.utils import reverse +from swh.web.tests.strategies import ( + content_swhid, + directory_swhid, + revision_swhid, + unknown_directory, +) +from swh.web.tests.utils import check_html_get_response + + +@given(content_swhid()) +def test_content_swhid_iframe(client, content_swhid): + url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)}) + check_html_get_response( + client, url, status_code=200, template_used="misc/iframe.html" + ) + + +@given(content_swhid()) +def test_content_core_swhid_iframe(client, content_swhid): + content_core_swhid = CoreSWHID( + object_type=content_swhid.object_type, object_id=content_swhid.object_id + ) + url = reverse("swhid-iframe", url_args={"swhid": str(content_core_swhid)}) + check_html_get_response( + client, url, status_code=200, template_used="misc/iframe.html" + ) + + +@given(directory_swhid()) +def test_directory_swhid_iframe(client, directory_swhid): + url = reverse("swhid-iframe", url_args={"swhid": str(directory_swhid)}) + check_html_get_response( + client, url, status_code=200, template_used="misc/iframe.html" + ) + + +@given(directory_swhid()) +def test_directory_core_swhid_iframe(client, directory_swhid): + directory_core_swhid = CoreSWHID( + object_type=directory_swhid.object_type, object_id=directory_swhid.object_id + ) + url = reverse("swhid-iframe", url_args={"swhid": str(directory_core_swhid)}) + check_html_get_response( + client, url, status_code=200, template_used="misc/iframe.html" + ) + + +@given(revision_swhid()) +def test_iframe_unsupported_object(client, revision_swhid): + url = reverse("swhid-iframe", url_args={"swhid": str(revision_swhid)}) + check_html_get_response( + client, url, status_code=400, template_used="misc/iframe.html" + ) + + +@given(unknown_directory()) +def test_iframe_object_not_found(client, unknown_directory): + swhid = CoreSWHID( + object_type=ObjectType.DIRECTORY, object_id=hash_to_bytes(unknown_directory) + ) + url = reverse("swhid-iframe", url_args={"swhid": str(swhid)}) + check_html_get_response( + client, url, status_code=404, template_used="misc/iframe.html" + ) + + +@given(content_swhid()) +def test_swhid_iframe_unknown_error(client, mocker, content_swhid): + mocker.patch("swh.web.misc.iframe.get_swhid").side_effect = Exception("Error") + url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)}) + check_html_get_response( + client, url, status_code=500, template_used="misc/iframe.html" + ) diff --git a/swh/web/tests/strategies.py b/swh/web/tests/strategies.py --- a/swh/web/tests/strategies.py +++ b/swh/web/tests/strategies.py @@ -23,6 +23,7 @@ from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_bytes, hash_to_hex from swh.model.hypothesis_strategies import origins as new_origin_strategy from swh.model.hypothesis_strategies import snapshots as new_snapshot +from swh.model.identifiers import ObjectType from swh.model.model import ( Content, Directory, @@ -646,3 +647,51 @@ "rev_dir_rev_path": "libtess2", } ) + + +def swhid(): + """ + Hypothesis strategy returning a qualified SWHID for any object + ingested into the test archive. + """ + return _known_swh_object("swhids") + + +def content_swhid(): + """ + Hypothesis strategy returning a qualified SWHID for a content object + ingested into the test archive. + """ + return swhid().filter(lambda swhid: swhid.object_type == ObjectType.CONTENT) + + +def directory_swhid(): + """ + Hypothesis strategy returning a qualified SWHID for a directory object + ingested into the test archive. + """ + return swhid().filter(lambda swhid: swhid.object_type == ObjectType.DIRECTORY) + + +def release_swhid(): + """ + Hypothesis strategy returning a qualified SWHID for a release object + ingested into the test archive. + """ + return swhid().filter(lambda swhid: swhid.object_type == ObjectType.RELEASE) + + +def revision_swhid(): + """ + Hypothesis strategy returning a qualified SWHID for a revision object + ingested into the test archive. + """ + return swhid().filter(lambda swhid: swhid.object_type == ObjectType.REVISION) + + +def snapshot_swhid(): + """ + Hypothesis strategy returning a qualified SWHID for a snapshot object + ingested into the test archive. + """ + return swhid().filter(lambda swhid: swhid.object_type == ObjectType.SNAPSHOT)