from json import loads
from plone import api
from plone.app.iterate.interfaces import IWorkingCopy
from plone.app.testing import logout
from ploneintranet import api as pi_api
from ploneintranet.api.tests.mocks import FakeUserprofile as FUP
from ploneintranet.layout.browser import utils
from ploneintranet.layout.interfaces import INoBarcelonetaLayer
from ploneintranet.layout.interfaces import IPloneintranetLayoutLayer
from ploneintranet.layout.testing import IntegrationTestCase
from ploneintranet.layout.testing import IntegrationTestCaseQRP
from ploneintranet.layout.utils import in_app
from ploneintranet.testing import dummy_file
from ploneintranet.testing import temporary_registry_record
from Products.Five import BrowserView
from unittest import mock
from zExceptions import NotFound
from zope.i18n import translate
from zope.interface import alsoProvides
from zope.interface import noLongerProvides

import json
import lxml
import re


class AppWithParametersView(BrowserView):
    def __call__(self):
        return self.request.form


class FakeUserprofile(FUP):
    username = "testuser"
    fullname = "mr. testuser"
    initials = "mrt"

    def getUserName(self):
        return self.username


class TestViews(IntegrationTestCase):
    def setUp(self):
        """Custom shared utility setup for tests."""
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.folder = api.content.create(
            container=self.portal, type="Folder", title="Test contextless folder"
        )
        self.create_apps()
        alsoProvides(self.request, IPloneintranetLayoutLayer)

    def create_apps(self):
        """We create addition apps for testing purposes"""
        for app in [
            {"title": "Empty app", "app": ""},
            {"title": "Private app", "app": "@@sitemap"},
            {"title": "Public app", "app": "@@proto"},
            {
                "title": "App with parameters",
                "app": "@@app-with-parameters",
                "app_parameters": '{"foo": "bar"}',
            },
            {"title": "Conditional App", "app": "@@proto", "condition": "python:False"},
            {
                "title": "Redirect App",
                "app": "@@app-redirect-to-url",
                "app_parameters": '{"url": "https://www.example.com"}',
            },
        ]:
            api.content.create(
                self.portal.apps,
                type="ploneintranet.layout.app",
                title=app["title"],
                app=app["app"],
                app_parameters=app.get("app_parameters", ""),
                condition=app.get("condition", ""),
            )
        api.content.transition(self.portal.apps["public-app"], to_state="published")

    def get_view(self, name, obj=None, **params):
        """Retutn a view with a fresh request on the context of obj
        If obj is None use the portal
        """
        if obj is None:
            obj = self.portal
        request = self.request.clone()
        request.form.update(params)
        return api.content.get_view(name, obj, request)

    def test_date_picker_i18n_json(self):
        """We want pat-date-picker i18n"""
        view = self.get_view("date-picker-i18n.json")
        observed = loads(view())
        expected = {
            "nextMonth": "next_month_link",
            "previousMonth": "prev_month_link",
            "months": [
                "January",
                "February",
                "March",
                "April",
                "May",
                "June",
                "July",
                "August",
                "September",
                "October",
                "November",
                "December",
            ],
            "weekdays": [
                "Sunday",
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday",
                "Saturday",
            ],
            "weekdaysShort": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
        }
        self.assertDictEqual(observed, expected)

    def test_dashboard_tiles(self):
        """Check if the dashboard tiles are correctly configured
        through the registry
        """
        view = self.get_view("dashboard.html")
        self.assertTupleEqual(
            view.activity_tiles(),
            (
                "./@@contacts_search.tile",
                "./@@activitystream.dashboard.tile",
                "./@@news.tile",
                "./@@bookmarks.workspaces.tile",
                "./@@bookmarks.apps.tile",
                "./@@bookmarks.tile",
                "./@@my_documents.tile",
            ),
        )
        self.assertTupleEqual(
            view.task_tiles(),
            (
                "./@@contacts_search.tile",
                "./@@news.tile",
                "./@@my_documents.tile",
                "./@@workspaces.tile?workspace_type=ploneintranet.workspace.workspacefolder",  # noqa: E501
                "./@@workspaces.tile?workspace_type=ploneintranet.workspace.case",
                "./@@events.tile",
                "./@@tasks.tile",
            ),
        )

    def test_apps_view(self):
        """Check the @@apps view

        This is tricky, because apps may register tiles outside of
        this package ploneintranet.layout, but this package should NOT
        have any outside dependencies (to avoid dependency loops).
        """
        view = self.get_view("apps.html")
        found = {app.getId() for app in view.apps()}
        configured = {"case-manager", "messages", "preferences"}
        # We want all the configured app to be really there
        # there may be more e.g. bookmarks but out of test scope here
        self.assertSetEqual(configured.difference(found), set())

    def test_app_view(self):
        app_view = self.get_app_view("private-app")
        self.assertIn('<h1 class="documentFirstHeading">Site map</h1>', app_view())

    def test_app_view_parameters(self):
        # This is a testing app with parameters that return the parameters
        app_view = self.get_app_view("app-with-parameters")
        self.assertDictEqual(app_view(), {"foo": "bar"})

    def test_app_redirect(self):
        app_view = self.get_app_view("redirect-app")
        self.assertEqual(app_view(), "https://www.example.com")

    def get_app_view(self, app_id):
        """Return the app view for the given app_id"""
        app = self.portal.apps[app_id]
        return app.app_view(self.request)

    def get_app_tile(self, app_id):
        """Return the app tile view for the given app_id"""
        app = self.portal.apps[app_id]
        return api.content.get_view("app-tile", app, self.request.clone())

    def test_app_basetile_not_found(self):
        """Check the not_found property of the app tile adapter"""
        # The path is empty, so we have nothing to look for
        tile = self.get_app_tile("empty-app")
        self.assertTrue(tile.not_found)

        # if we set a path, we have to find it even if we are anonymous
        tile = self.get_app_tile("private-app")
        self.assertFalse(tile.not_found)

        tile = self.get_app_tile("private-app")
        with api.env.adopt_roles({"Anonymous"}):
            self.assertFalse(tile.not_found)

    def test_app_basetile_unauthorized(self):
        """Check the unauthorized property of the app tile adapter"""
        # If we have not set a path, we cannot traverse to anything,
        # so we cannot say if it is authorized or not
        tile = self.get_app_tile("empty-app")
        with self.assertRaises(NotFound):
            tile.unauthorized

        # If we set an existing path, we will have a different response
        # according to our roles in context
        tile = self.get_app_tile("private-app")
        self.assertFalse(tile.unauthorized)

        tile = self.get_app_tile("private-app")
        with api.env.adopt_roles({"Anonymous"}):
            self.assertTrue(tile.unauthorized)

    def test_app_basetile_modal(self):
        """Check the modal property of the app tile adapter"""
        # With an empty path, when clicking on a tile,
        # we will get an alert in a modal
        tile = self.get_app_tile("empty-app")
        self.assertEqual(tile.modal, "pat-modal")

        # Otherwise we will open the tile
        tile = self.get_app_tile("private-app")
        self.assertEqual(tile.modal, "")

        # Even if we are unauthorized
        tile = self.get_app_tile("private-app")
        with api.env.adopt_roles({"Anonymous"}):
            self.assertEqual(tile.modal, "pat-modal")

    def test_app_basetile_url(self):
        """Check the url property of the app tile adapter"""
        # With an empty path, when clicking on a tile,
        # we will get an alert in a modal
        tile = self.get_app_tile("empty-app")
        self.assertEqual(
            tile.url, "http://nohost/plone/@@app-not-available.html#document-content"
        )

        # Otherwise we will open the tile
        tile = self.get_app_tile("private-app")
        self.assertEqual(tile.url, "http://nohost/plone/apps/private-app/@@sitemap")

        # Even if we are unauthorized
        tile = self.get_app_tile("private-app")
        with api.env.adopt_roles({"Anonymous"}):
            self.assertEqual(
                tile.url, "http://nohost/plone/@@app-unauthorized#document-content"
            )

    def test_app_basetile_disabled(self):
        """Check the disabled property of the app tile adapter"""
        # The tile should be disabled if path is not set (default)
        tile = self.get_app_tile("empty-app")
        self.assertEqual(tile.disabled, "disabled")

        # The tile should be enabled because the path is allowed
        tile = self.get_app_tile("private-app")
        self.assertEqual(tile.disabled, "")

        # The tile should be disabled if the path is not allowed
        tile = self.get_app_tile("private-app")
        with api.env.adopt_roles({"Anonymous"}):
            self.assertEqual(tile.disabled, "disabled")

    def test_app_basetile_condition(self):
        """Check the condition property of the app tile adapter"""
        tile = self.get_app_tile("conditional-app")
        self.assertFalse(tile.condition())

    def test_webstats_js(self):
        """Check if the view works and if it is correctly cached"""
        OLD_JS = api.portal.get_registry_record("plone.webstats_js")
        view1_portal = self.get_view("webstats_js")
        view1_folder = self.get_view("webstats_js", obj=self.folder)
        # Test empty registry record
        self.assertIn(OLD_JS, view1_portal())
        self.assertIn(OLD_JS, view1_folder())

        NEW_JS = "<div>webstats_js</div>"
        view2_portal = self.get_view("webstats_js")
        with temporary_registry_record("plone.webstats_js", NEW_JS):
            # This comes from cache
            self.assertIn(OLD_JS, view1_portal())
            self.assertIn(OLD_JS, view1_folder())
            # this does not
            self.assertIn(NEW_JS, view2_portal())

    def test_dashboard_view_default_dashboard(self):
        """Check the various methods of the dashboard view"""
        view = self.get_view("dashboard.html")
        # we are not a membrane user so persistency will not work
        # the default is to return activity
        self.assertEqual(pi_api.userprofile.get_current(), None)
        self.assertEqual(view.default_dashboard(), "activity")

        # but we can ask for everything
        view = self.get_view("dashboard.html", dashboard="test")
        self.assertEqual(view.default_dashboard(), "test")

        # now we use a fake user for testing the persistency
        with mock.patch(
            "ploneintranet.api.userprofile.get_current", return_value=FakeUserprofile()
        ):
            view = self.get_view("dashboard.html", dashboard="evil")
            self.assertEqual(view.default_dashboard(), "evil")
            user = pi_api.userprofile.get_current()
            # value is not set because it is not a good one
            with self.assertRaises(AttributeError):
                user.dashboard_default
            # if we specify a good one it will be set correctly
            view = self.get_view("dashboard.html", dashboard="task")
            self.assertEqual(view.default_dashboard(), "task")
            self.assertEqual(user.dashboard_default, "task")
            # now the view default persists to task, and not to activity
            view = self.get_view("dashboard.html")
            self.assertEqual(view.default_dashboard(), "task")
            self.assertEqual(user.dashboard_default, "task")

    def test_custom_dashboard(self):
        # We need a fake user to test persist tile ordering and display options
        with mock.patch(
            "ploneintranet.api.userprofile.get_current", return_value=FakeUserprofile()
        ):
            user = pi_api.userprofile.get_current()
            view = self.get_view("dashboard.html", dashboard="custom")
            available_custom_tiles = list(view.available_custom_tiles())
            reversed_available_custom_tiles = available_custom_tiles[::-1]
            self.assertListEqual(
                list(view.custom_tiles().keys()), available_custom_tiles
            )
            # The view is called
            # Use regex to match different order of query parameters
            self.assertIsNotNone(
                re.search(
                    r"portlet span-1 state-collapsed category-publication app-news",
                    view(),
                )
            )
            # and we do not write anything on the user
            self.assertRaises(AttributeError, getattr, user, "custom_tiles")
            # We now test if we can modify the order of the tiles
            params = {"display-%s" % tile: "span-2" for tile in available_custom_tiles}
            params["tiles_order"] = reversed_available_custom_tiles
            edit = self.get_view("edit-dashboard", **params)
            edit.maybe_update_tiles()

            # Now the data persist on the user
            self.assertEqual(
                list(user.custom_tiles.keys()), reversed_available_custom_tiles
            )
            self.assertEqual(user.custom_tiles["./@@news.tile"]["display"], "span-2")
            # Check that custom url with unicode title do not break the dashboard
            view.request.__annotations__.clear()
            with temporary_registry_record(
                "ploneintranet.layout.dashboard_custom_tiles",
                ("./@@bookmarks.workspaces.tile?title=Arbeitsr\xe4ume",),
            ):
                self.assertIn("title=Arbeitsr%C3%A4ume", view())

    def test_in_app_dashboard(self):
        view = self.get_view("dashboard.html")
        self.assertFalse(in_app(view))

    def test_in_app_apptile(self):
        tile = self.get_app_tile("empty-app")
        self.assertTrue(in_app(tile))

    def test_in_app_dashboard_context(self):
        view = self.get_view("dashboard.html")
        self.assertFalse(in_app(view.context))

    def test_in_app_apptile_context(self):
        tile = self.get_app_tile("empty-app")
        self.assertTrue(in_app(tile.context))

    def test_proto_slow(self):
        """the is_slow function"""
        with temporary_registry_record(
            "ploneintranet.layout.known_bad_ips", ("666.666.666.666",)
        ):
            proto = self.get_view("proto")
            self.assertFalse(proto.is_slow())
            proto.request.__annotations__.pop("plone.memoize")
            proto.request.environ.update({"REMOTE_ADDR": "666.666.666.666 1"})
            self.assertTrue(proto.is_slow())

    def test_proto_plone_version(self):
        """Test plone_version property of @@proto view."""
        proto = self.get_view("proto")
        self.assertTrue(proto.min_plone_version("1.9999.9999.beta7777"))
        self.assertFalse(proto.min_plone_version("9999.1"))

    def test_proto_redactor(self):
        """Test redactor options with @@proto."""
        proto = self.get_view("proto")
        options = proto.redactor_options()
        # option string to dict
        options = dict([it.split(": ") for it in options.split("; ")])
        self.assertTrue(
            options.pop("imageupload").startswith(
                "http://nohost/@@images-upload.json?_authenticator="
            )
        )
        self.assertDictEqual(
            options,
            {
                "buttons": "undo, redo, format, bold, italic, deleted, lists, link, line, image",  # noqa: E501
                "formatting": "p, h1, h2, h3, blockquote, pre",
                "imagegetjson": "http://nohost/@@images.json",
                "imageresizable": "true",
                "plugins": "alignment, table, video, imagemanager",
                "toolbar-external": "#editor-toolbar",
            },
        )

    def test_proto_redactor_html_allow(self):
        """Test redactor options with @@proto."""
        api.portal.set_registry_record(
            "ploneintranet.layout.editor_html_allowed_users", ("testuser",)
        )
        with mock.patch("plone.api.user.get_current", return_value=FakeUserprofile()):
            proto = self.get_view("proto")
            options = proto.redactor_options()
            # option string to dict
            options = dict([it.split(": ") for it in options.split("; ")])
            self.assertTrue(
                options.pop("imageupload").startswith(
                    "http://nohost/@@images-upload.json?_authenticator="
                )
            )
            self.assertDictEqual(
                options,
                {
                    "buttons": "undo, redo, format, bold, italic, deleted, lists, link, line, image, html",  # noqa: E501
                    "formatting": "p, h1, h2, h3, blockquote, pre",
                    "imagegetjson": "http://nohost/@@images.json",
                    "imageresizable": "true",
                    "plugins": "alignment, table, video, imagemanager",
                    "show-source-button": "true",
                    "toolbar-external": "#editor-toolbar",
                },
            )

    def test_proto_site_title(self):
        """Check the site_title property in the proto view"""
        self.assertEqual(self.get_view("proto").site_title, "Intranet")

        with temporary_registry_record(
            "ploneintranet.layout.quaive_organisation", "Fòò"
        ):
            self.assertEqual(self.get_view("proto").site_title, "Fòò Intranet")

            with temporary_registry_record("ploneintranet.layout.quaive_title", ""):
                self.assertEqual(self.get_view("proto").site_title, "Fòò")

        # Default to the Plone way is nothing is set
        with temporary_registry_record("ploneintranet.layout.quaive_title", ""):
            self.assertEqual(self.get_view("proto").site_title, "Plone site")

    def test_splashpage(self):
        with mock.patch(
            "ploneintranet.api.userprofile.get_current", return_value=FakeUserprofile()
        ):
            with temporary_registry_record(
                "ploneintranet.layout.splashpage_enabled", True
            ):
                view = self.get_view("dashboard.html")
                self.assertEqual(view.splashpage_uid, "splashpage-1")
                self.assertTrue(view.show_splashpage)
                # This emulates the user closing the splashpage
                view.request = self.request.clone()
                view.request.form["splashpage_uid"] = "splashpage-1"
                view()
                self.assertFalse(view.show_splashpage)
                # Check if we can force the splashpage to appear again
                view.request = self.request.clone()
                view.request.form["splashpage_uid"] = "force"
                view()
                self.assertTrue(view.show_splashpage)

    def test_toggle_lock(self):
        # The portal_catalog does not support TTW locking
        view = self.get_view("toggle-lock", obj=self.portal.portal_catalog)
        self.assertEqual(view.lock_info, None)
        self.assertEqual(view.lock_operations, None)

        # A Document does support TTW locking
        obj = api.content.create(
            title="Example page", container=self.folder, type="Document"
        )
        view = self.get_view("toggle-lock", obj)
        self.assertEqual(view.lock_info, None)
        self.assertNotEqual(view.lock_operations, None)
        view.lock()
        # Purge the cache
        view.request.__annotations__.pop("plone.memoize")
        self.assertEqual(view.lock_info["creator"], "test_user_1_")
        view.unlock()
        # Purge the cache
        view.request.__annotations__.pop("plone.memoize")
        self.assertEqual(view.lock_info, None)

    def test_on_screen_help(self):
        view = self.get_view("on-screen-help")
        # Test that we can create bubble links if the bubble is well known
        self.assertEqual(view.link_to(1), "")
        self.assertIn("Help", view.link_to("hamburger"))

        # Check if we have a default bubble with no or wrong parameters
        self.assertIn(view._fallback_bubble["title"], view())
        view = self.get_view("on-screen-help", q="1")
        self.assertIn(view._fallback_bubble["title"], view())
        view = self.get_view("on-screen-help", q="hamburger")
        self.assertIn(translate(view.bubbles["hamburger"]["description"]), view())

        # check that we can set up custom bubbles
        custom_bubbles_json = (
            '{"foo-bar": {' '    "title": "Foo", "description": "<p>Bar baz</p>"' "}}"
        )
        self.assertNotIn("foo-bar", view.bubbles)
        with temporary_registry_record(
            "ploneintranet.layout.custom_bubbles", custom_bubbles_json
        ):
            view = self.get_view("on-screen-help", q="foo-bar")
            self.assertIn("foo-bar", view.bubbles)
            self.assertIn("Bar baz", view())

        # We can disable the bubbles through the registry
        with temporary_registry_record(
            "ploneintranet.layout.bubbles_enabled", "Disabled"
        ):
            view = self.get_view("on-screen-help")
            self.assertDictEqual(view.bubbles, {})

        # We can force the bubbles through the registry
        with temporary_registry_record("ploneintranet.layout.bubbles_enabled", "On"):
            page = self.portal.restrictedTraverse("test_rendering")()
            self.assertIn(
                "osh-on", lxml.html.fromstring(page).find(".//body").attrib["class"]
            )

    def test_doc_preview_info(self):
        obj = api.content.create(type="Document", title="Test", container=self.portal)

        request = self.request.clone()
        view = api.content.get_view("previews", obj, request)
        preview_storage = view.preview_storage
        self.assertTrue(view.is_allowed_document_type())

        # Fake converting in process
        preview_storage.converting = True
        self.assertEqual(view.preview_info, "Page views being generated.")

        # Fake no converting in process and no error
        view.request.__annotations__.clear()
        preview_storage.converting = False
        self.assertEqual(
            view.preview_info,
            "It was not possible to generate page views for this document.",
        )
        # Fake no converting in process and known error
        view.request.__annotations__.clear()
        preview_storage.exception_msg = "Command Line Error: Incorrect password"
        self.assertEqual(
            view.preview_info,
            "Cannot generate preview because "
            "the file appears to be password protected.",
        )
        # Fake no converting in process and unknown error
        view.request.__annotations__.clear()
        preview_storage.exception_msg = "Custom error message"
        self.assertEqual(
            view.preview_info,
            "It was not possible to generate page views for this document.",
        )

    def test_image_preview_info(self):
        obj = api.content.create(type="Image", title="Test", container=self.portal)

        request = self.request.clone()
        view = api.content.get_view("previews", obj, request)
        self.assertFalse(view.is_allowed_document_type())
        self.assertEqual(
            view.preview_info,
            "Page views are not supported for this file format.",
        )

    def test_audio_preview(self):
        """Test if audio files are rendered with an audio player."""
        obj = api.content.create(
            type="File",
            title="Test",
            file=dummy_file("test.mp3"),
            container=self.portal,
        )
        request = self.request.clone()
        view = api.content.get_view("previews", obj, request)

        tree = lxml.html.fromstring(view())
        el_audio = tree.xpath("//audio/source")
        self.assertEqual(len(el_audio), 1)
        self.assertIn("test.mp3", el_audio[0].attrib["src"])

    def test_video_preview(self):
        """Test if video files are rendered with a video player."""
        obj = api.content.create(
            type="File",
            title="Test",
            file=dummy_file("test.mp4"),
            container=self.portal,
        )
        request = self.request.clone()
        view = api.content.get_view("previews", obj, request)

        tree = lxml.html.fromstring(view())
        el_video = tree.xpath("//video/source")
        self.assertEqual(len(el_video), 1)
        self.assertIn("test.mp4", el_video[0].attrib["src"])

        el_info = tree.xpath("//p[contains(@class, 'message no-preview')]")
        self.assertEqual(len(el_info), 0)

    def test_require_login(self):
        """We have a custom require login to handle faux requests to login
        on objects that we actually can view
        see https://github.com/quaive/ploneintranet/issues/2388
        """
        view = self.get_view("require_login")
        # No came from specified, go to the insufficient privilegs page
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/insufficient-privileges",
        )

        # Check that a user that can actually view is given a second chance
        view = self.get_view("require_login", came_from="/plone/apps/messages")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/apps/messages",
        )

        view = self.get_view(
            "require_login", came_from="http://nohost/plone/apps/messages"
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/apps/messages",
        )

        # check also with query strings
        view = self.get_view(
            "require_login",
            came_from="/plone/apps/messages?q=1",
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/apps/messages?q=1",
        )

        view = self.get_view(
            "require_login",
            came_from="http://nohost/plone/apps/messages?q=1",
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/apps/messages?q=1",
        )

        # Check that a user that cannot view receives the insufficient privileges
        with api.env.adopt_roles(["Member"]):
            view = self.get_view("require_login", came_from="/plone/apps/private-app")
            view()
            self.assertEqual(
                view.request.response.headers["location"],
                "http://nohost/plone/insufficient-privileges",
            )

            view = self.get_view(
                "require_login", came_from="http://nohost/plone/apps/private-app"
            )
            view()
            self.assertEqual(
                view.request.response.headers["location"],
                "http://nohost/plone/insufficient-privileges",
            )

        # Do not break on broken came from
        view = self.get_view("require_login", came_from="/plone/foo")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/insufficient-privileges",
        )

        view = self.get_view("require_login", came_from="http://nohost/plone/foo")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/insufficient-privileges",
        )

        # Avoid endless loops
        view = self.get_view("require_login", came_from="/plone/require_login")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/insufficient-privileges",
        )

        view = self.get_view(
            "require_login", came_from="http://nohost/plone/require_login"
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/insufficient-privileges",
        )

        # Check that anonymous is redirected to the login page
        logout()

        view = self.get_view("require_login", came_from="/plone/apps/messages")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/login?came_from=/plone/apps/messages",
        )

        view = self.get_view(
            "require_login", came_from="http://nohost/plone/apps/messages"
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/login?came_from=http%3A//nohost/plone/apps/messages",
        )

        # check also with query strings
        view = self.get_view("require_login", came_from="/plone/apps/messages?q=1")
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/login?came_from=/plone/apps/messages%3Fq%3D1",
        )

        view = self.get_view(
            "require_login", came_from="http://nohost/plone/apps/messages?q=1"
        )
        view()
        self.assertEqual(
            view.request.response.headers["location"],
            "http://nohost/plone/login?came_from=http%3A//nohost/plone/apps/messages%3Fq%3D1",  # noqa: E501
        )

    def test_ploneintranet_theme_enabled(self):
        """Test, if the `ploneintranet_enabled` view is available."""
        request = self.request.clone()
        view = api.content.get_view("ploneintranet_enabled", self.portal, request)
        self.assertEqual(view(), "ok")

    def test_ploneintranet_theme_not_enabled(self):
        """Test, if the `ploneintranet_enabled` view is not available if the
        INoBarcelonetaLayer layer is missing.
        """
        request = self.request.clone()
        noLongerProvides(request, INoBarcelonetaLayer)
        self.portal.REQUEST = request

        with self.assertRaises(api.exc.InvalidParameterError):
            api.content.get_view("ploneintranet_enabled", self.portal, request)


class TestViewsQRP(IntegrationTestCaseQRP):
    def setUp(self):
        """Custom shared utility setup for tests."""
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        alsoProvides(self.request, IPloneintranetLayoutLayer)

    def get_view(self, name, obj=None, **params):
        """Retutn a view with a fresh request on the context of obj If obj is
        None use the portal."""
        if obj is None:
            obj = self.portal
        request = self.request.clone()
        request.form.update(params)
        return api.content.get_view(name, obj, request)

    def test_unauthorized_view(self):
        # Without query parameters
        view = self.get_view("unauthorized")
        tree = lxml.html.fromstring(view())
        el_login = tree.xpath("//a[contains(@class, 'session-expired__login_link')]")
        self.assertEqual(len(el_login), 1)
        self.assertEqual("http://nohost/plone/login", el_login[0].attrib["href"])

        # With came_from
        view = self.get_view(
            "unauthorized", came_from="http://nohost/plone/apps/messages"
        )
        tree = lxml.html.fromstring(view())
        el_login = tree.xpath("//a[contains(@class, 'session-expired__login_link')]")
        self.assertEqual(len(el_login), 1)
        self.assertEqual(
            "http://nohost/plone/login?came_from=http%3A%2F%2Fnohost%2Fplone%2Fapps%2Fmessages",  # noqa: E501
            el_login[0].attrib["href"],
        )

        # check also with query strings
        view = self.get_view("unauthorized", came_from="/plone/apps/messages", q=1)
        tree = lxml.html.fromstring(view())
        el_login = tree.xpath("//a[contains(@class, 'session-expired__login_link')]")
        self.assertEqual(len(el_login), 1)
        self.assertEqual(
            "http://nohost/plone/login?came_from=%2Fplone%2Fapps%2Fmessages&q=1",
            el_login[0].attrib["href"],
        )

        view = self.get_view(
            "unauthorized", came_from="http://nohost/plone/apps/messages", q=1
        )
        tree = lxml.html.fromstring(view())
        el_login = tree.xpath("//a[contains(@class, 'session-expired__login_link')]")
        self.assertEqual(len(el_login), 1)
        self.assertEqual(
            "http://nohost/plone/login?came_from=http%3A%2F%2Fnohost%2Fplone%2Fapps%2Fmessages&q=1",  # noqa: E501
            el_login[0].attrib["href"],
        )

    def test_proto_email_logo_url(self):
        """Check the logo URL that is used when sending emails"""
        view = self.get_view("proto")
        self.assertEqual(
            view.email_logo_url,
            (
                "http://nohost/plone/++theme++ploneintranet.theme/"
                "generated/assets/default/mail/header.png"
            ),
        )

    def test_proto_email_logo_url_zegg(self):
        api.portal.set_registry_record("ploneintranet.theme.brand", "zegg")
        view = self.get_view("proto")
        self.assertEqual(
            view.email_logo_url,
            (
                "http://nohost/plone/++theme++ploneintranet.theme/"
                "generated/assets/zegg/mail/header.png"
            ),
        )

    def test_proto_email_logo_url_bogus(self):
        api.portal.set_registry_record("ploneintranet.theme.brand", "bogus")
        view = self.get_view("proto")
        self.assertEqual(view.email_logo_url, "")


class TestImageUpload(IntegrationTestCase):
    def setUp(self):
        """Custom shared utility setup for tests."""
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.folder = api.content.create(
            container=self.portal, type="Folder", title="test"
        )
        alsoProvides(self.request, IPloneintranetLayoutLayer)

    def testImageUploadSingle__redactor2(self):
        """Test image upload.
        Original from: plone.app.contents.tests.test_widgets
        """
        view = utils.ImageUploadJson(self.folder, self.request)
        from plone.namedfile.file import FileChunk

        chunk = FileChunk(b"GIF89a;")
        chunk.filename = "test.gif"
        self.request.form["file"] = chunk
        self.request.REQUEST_METHOD = "POST"
        data = json.loads(view())

        self.assertIn("file-0", data)
        self.assertIn("url", data["file-0"])
        self.assertEqual(
            data["file-0"]["url"],
            "http://nohost/plone/test/test.gif/@@images/image/3col",
        )
        self.assertIsNotNone(data["file-0"]["id"])

    def testImageUploadSingle__redactor3(self):
        """Test image upload.
        Original from: plone.app.contents.tests.test_widgets
        Redactor 3 uses ``file[]`` instead of ``file``.
        """
        view = utils.ImageUploadJson(self.folder, self.request)
        from plone.namedfile.file import FileChunk

        chunk = FileChunk(b"GIF89a;")
        chunk.filename = "test.gif"
        self.request.form["file[]"] = chunk
        self.request.REQUEST_METHOD = "POST"
        data = json.loads(view())

        self.assertIn("file-0", data)
        self.assertIn("url", data["file-0"])
        self.assertEqual(
            data["file-0"]["url"],
            "http://nohost/plone/test/test.gif/@@images/image/3col",
        )
        self.assertIsNotNone(data["file-0"]["id"])

    def testImageUploadMultiple(self):
        """Test image upload.
        Original from: plone.app.contents.tests.test_widgets
        """
        view = utils.ImageUploadJson(self.folder, self.request)
        from plone.namedfile.file import FileChunk

        chunk1 = FileChunk(b"GIF89a;")
        chunk1.filename = "test1.gif"
        chunk2 = FileChunk(b"GIF89a;")
        chunk2.filename = "test2.gif"
        self.request.form["file[]"] = [chunk1, chunk2]
        self.request.REQUEST_METHOD = "POST"
        data = json.loads(view())

        self.assertIn("file-0", data)
        self.assertIn("url", data["file-0"])
        self.assertEqual(
            data["file-0"]["url"],
            "http://nohost/plone/test/test1.gif/@@images/image/3col",
        )
        self.assertIsNotNone(data["file-0"]["id"])

        self.assertIn("file-1", data)
        self.assertIn("url", data["file-1"])
        self.assertEqual(
            data["file-1"]["url"],
            "http://nohost/plone/test/test2.gif/@@images/image/3col",
        )
        self.assertIsNotNone(data["file-1"]["id"])


class TestExceptionView(IntegrationTestCase):
    def test_exception_ajax_json(self):
        """Test, if non text/html (e.g. ajax) requests which lead to exceptions
        are rendered as JSON in the default case.
        """
        request = self.layer["request"]
        request.environ["HTTP_ACCEPT"] = "*/*"
        view = api.content.get_view("index.html", Exception(), request)
        ret = view()
        self.assertEqual(ret, '{"error_type": "Exception"}')
        self.assertEqual(request.response.getHeader("Content-Type"), "application/json")

    def test_exception_html(self):
        """Test, if text/html requests which lead to exceptions are
        rendered as text/html in any other case.
        """
        request = self.layer["request"]
        request.environ["HTTP_ACCEPT"] = "text/html"
        view = api.content.get_view("index.html", Exception(), request)
        ret = view()
        self.assertIn("<html", ret)
        # Content-Type is something like "text/html; charset=utf-8"
        self.assertIn("text/html", request.response.getHeader("Content-Type"))


class TestWorkflowMenu(IntegrationTestCase):
    def test_not_available_if_no_workflow(self):
        """The workflow menu should not be available if there is no workflow"""
        view = api.content.get_view("workflow_menu", self.portal, self.request)
        self.assertFalse(view.has_workflow())
        self.assertFalse(view.available())

    def test_not_available_if_working_copy(self):
        """The workflow menu should not be available if this is a working copy"""
        view = api.content.get_view(
            "workflow_menu", self.portal.apps.preferences, self.request
        )
        self.assertTrue(view.has_workflow())
        self.assertTrue(view.available())

        # Now declare this to be a working copy and clean the cache
        alsoProvides(view.context, IWorkingCopy)
        view.request.__annotations__.clear()

        self.assertTrue(view.has_workflow())
        self.assertFalse(view.available())
