#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/common/pywrap", sys.argv)
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import os
import subprocess

import testlib

INSTALL_RPMS = [
    "empty-1-0.noarch",
    "chrony-0.1-2.noarch",
    "tzdata-99999-2.noarch",
]

REPO_LOCATION = "/var/local-repo"
CHECKOUT_LOCATION = "/var/local-tree"
RPM_LOCATION = "/usr/share/rpm"
KEY_ID = "95A8BA1754D0E95E2B3A98A7EE15015654780CBD"


def switch_tab(b, index, tab):
    row_sel = f"#available-deployments > tbody:nth-child({index + 1})"
    b.wait_visible(row_sel)
    if not b.is_visible(f"{row_sel} .ct-listing-panel-tabs"):
        b.click(f"{row_sel} .pf-v5-c-table__toggle button")
        b.wait_visible(f"{row_sel} .ct-listing-panel-tabs")
    b.click(f"{row_sel} button:contains({tab})")


def wait_deployment_details_prop(b, index, tab, prop, value):
    switch_tab(b, index, tab)
    b.wait_text(f"#available-deployments > tbody:nth-child({index + 1}) {prop}", value)


def wait_deployment_prop(b, index, prop, value):
    deployment = f"#available-deployments > tbody:nth-child({index + 1})"
    if prop == "Actions":
        if value == "":
            b.wait_text(f"{deployment} tr:nth-child(1) td:last-child", value)
        else:
            b.wait_text(f"{deployment} tr:nth-child(1) td:last-child button", value)
    else:
        b.wait_text(f"{deployment} td[data-label={prop}]", value)


def wait_packages(b, index, packages):
    switch_tab(b, index, "Packages")
    for group, pkgs in packages.items():
        for pkg in pkgs:
            b.wait_in_text(f"#available-deployments > tbody:nth-child({index + 1}) .{group}", pkg)


def wait_not_packages(b, index, packages):
    switch_tab(b, index, "Packages")
    for pkg in packages:
        b.wait_not_in_text(f"#available-deployments > tbody:nth-child({index + 1})", pkg)


def check_package_count(b, assertIn, index):
    switch_tab(b, index, "Packages")
    for group in ['adds', 'removes']:
        b.call_js_func("ph_count_check", f"#available-deployments > tbody:nth-child({index + 1}) .{group} dd",  1)
    # 1 or 2, in some scenarios cockpit-ostree gets up/downgraded as well
    assertIn(b.call_js_func("ph_count", f"#available-deployments > tbody:nth-child({index + 1}) .up dd"), [1, 2])
    assertIn(b.call_js_func("ph_count", f"#available-deployments > tbody:nth-child({index + 1}) .down dd"), [1, 2])


def do_deployment_action(b, index, action):
    wait_deployment_prop(b, index, "Actions", action)
    b.click(f"#available-deployments > tbody:nth-child({index + 1}) tr:nth-child(1) td:last-child button")


def ensure_remote_http_port(m, remote="local"):
    remote_spec = m.execute(f"ostree remote show-url {remote} || true")
    if remote_spec.startswith("http"):
        parts = remote_spec.strip().split(":")
        port = parts[-1]
    else:
        if remote_spec:
            m.execute(["ostree", "remote", "delete", remote])
        m.execute(["ostree", "remote", "add", remote,
                   "http://127.0.0.1:12345", "--no-gpg-verify"])
        try:
            m.execute(["rpm-ostree reload"])
        except subprocess.CalledProcessError:
            m.execute("systemctl restart rpm-ostreed")
        port = 12345

    return port


def start_trivial_httpd(m, remote="local", location=REPO_LOCATION):
    port = ensure_remote_http_port(m, remote)
    pid = m.spawn(f"podman run -v {location}:/usr/local/nginx/html:ro,z -p {port}:80 quay.io/jitesoft/nginx", "httpd")

    m.execute(["ostree", "summary", f"--repo={location}", "-u"])
    m.wait_for_cockpit_running(port=port)
    return pid


def stop_trivial_httpd(m, pid):
    if pid:
        m.execute(["kill", str(pid)])


def generate_new_commit(m, pkg_to_remove):
    # Make one change of each type to a new rpm tree
    branch = m.execute(f"ostree refs --repo={REPO_LOCATION}").strip()

    m.upload([f"files/{k}.rpm" for k in INSTALL_RPMS],
             "/home/admin/")

    # move /usr/etc to /etc, makes rpm installs easier
    rpm_etc = os.path.join(CHECKOUT_LOCATION, "etc")
    usr_etc = os.path.join(CHECKOUT_LOCATION, "usr", "etc")
    m.execute(f"mv {usr_etc} {rpm_etc}")

    # Remove a package
    rpm_args = [CHECKOUT_LOCATION, RPM_LOCATION, pkg_to_remove]
    m.execute("rpm -e --verbose --root {0} --dbpath {1} {2}".format(*rpm_args))

    # Install our dummy packages, dbonly
    rpm_args[-1] = ' '.join([f"{os.path.join('/home/admin', x)}.rpm"
                             for x in INSTALL_RPMS])
    m.execute("rpm -U --oldpackage --root {0} --dbpath {1} --justdb {2}".format(*rpm_args))

    # move /etc back to /usr/etc to
    m.execute(f"mv {rpm_etc} {usr_etc}")

    # Upload a signing key
    m.upload(["files/secring.gpg",
              "files/pubring.gpg"], "/root/")

    m.execute(f"ostree commit -s cockpit-tree2 --repo {REPO_LOCATION} -b {branch} "
              "--add-metadata-string version=cockpit-base.2 "
              "--add-metadata-string rpmostree.inputhash=foo "
              f"--tree=dir={CHECKOUT_LOCATION} --gpg-sign={KEY_ID} --gpg-homedir=/root/",  timeout=600)
    m.execute(["ostree", "summary", f"--repo={REPO_LOCATION}", "-u"])


def rhsmcertd_hack(m):
    # HACK: https://github.com/candlepin/subscription-manager/issues/1404
    m.execute("systemctl disable rhsmcertd || true")
    m.execute("systemctl stop rhsmcertd || true")


def get_name(self):
    if self.image == "rhel4edge":
        return "rhel-edge"
    return "fedora-coreos"


class OstreeRestartCase(testlib.MachineCase):

    def testOstree(self):
        b = self.browser
        m = self.machine

        # Delete local remote so we start clean, without a file based remote
        ensure_remote_http_port(m)

        remove_pkg = m.execute("rpm -qa | grep socat").strip()

        rhsmcertd_hack(m)

        chrony = m.execute("rpm -qa | grep chrony").strip()
        tzdata = m.execute("rpm -qa | grep tzdata").strip()

        m.start_cockpit()
        b.login_and_go("/updates")
        b.enter_page("/updates")

        # Check current and rollback target
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_prop(b, 1, "Actions", "")

        wait_deployment_details_prop(b, 1, "Tree", "#osname", get_name(self))
        wait_deployment_details_prop(b, 1, "Tree", "#osversion", "cockpit-base.1")

        b.assert_pixels("#repo-remote-toolbar", "remote-toolbar")
        b.assert_pixels("#available-deployments > tbody:nth-child(2)", "deployment",
                        ignore=[".timestamp",
                                # The columns change size dependent on the second deployment's name.
                                "td[data-label=Name]", "td[data-label=State]"])

        wait_packages(b, 1, {"rpms-col1": [chrony], "rpms-col2": [tzdata]})
        wait_packages(b, 1, {"rpms-col2": [remove_pkg]})
        wait_not_packages(b, 1, INSTALL_RPMS)

        wait_deployment_details_prop(b, 1, "Signatures", ".no-signatures", "No signature available")

        # Require signatures
        m.execute("sed -i /gpg-verify/d /etc/ostree/remotes.d/local.conf")
        # HACK: rpm-ostree doesn't reload remote settings properly
        # https://github.com/projectatomic/rpm-ostree/issues/401
        m.execute("systemctl restart rpm-ostreed")

        b.wait_not_in_text("#available-deployments > tbody:nth-child(3) td[data-label=Name]", "cockpit")
        wait_deployment_prop(b, 2, "State", "Available")
        wait_deployment_prop(b, 2, "Actions", "Roll back and reboot")
        wait_deployment_details_prop(b, 2, "Tree", "#osname", get_name(self))

        # Check for new commit, get error
        b.wait_in_text("#check-for-updates-btn", "Check for updates")
        b.click("#check-for-updates-btn")
        b.wait_visible('#app .pf-v5-c-alert.pf-m-warning')
        b.wait_visible("#check-for-updates-btn:not(disabled)")

        # Serve repo
        server_pid = start_trivial_httpd(m)

        # Check for new commit
        b.click("#check-for-updates-btn")
        b.wait_visible("#check-for-updates-btn:disabled")
        b.wait_visible("#check-for-updates-btn:not(disabled)")

        # Generate new commit
        generate_new_commit(m, remove_pkg)

        # Check again not trusted
        b.click("#check-for-updates-btn")
        b.wait_visible("#check-for-updates-btn:not(disabled)")
        b.wait_in_text("#app .pf-m-danger", "Can't check signature: public key not found")

        m.upload(["files/publickey.asc"], "/root/")
        m.execute("ostree remote gpg-import local -k /root/publickey.asc")

        # Check again have update data
        b.click("#check-for-updates-btn")
        wait_deployment_prop(b, 1, "State", "Available")
        wait_deployment_prop(b, 1, "Actions", "Update and reboot")
        wait_deployment_prop(b, 2, "State", "Running")

        # Check update data
        wait_deployment_details_prop(b, 1, "Tree", "#osname", get_name(self))
        wait_deployment_details_prop(b, 1, "Tree", "#osversion", "cockpit-base.2")

        wait_packages(b, 1, {"up": ["tzdata-99999-2.noarch"],
                             "down": ["chrony-0.1-2.noarch"],
                             "adds": ["empty-1-0.noarch"],
                             "removes": [remove_pkg],
                             })
        check_package_count(b, self.assertIn, 1)

        # Check signatures
        switch_tab(b, 1, "Signatures")
        sel = "#available-deployments > tbody:nth-child(2)"
        b.wait_in_text(sel, KEY_ID)
        b.wait_in_text(sel, "RSA")
        b.wait_in_text(sel, "Cockpit Tester <do-not-reply@cockpit-project.org>")
        b.wait_in_text(sel, "Good signature")
        b.wait_in_text(sel, "When")

        # Force an error
        stop_trivial_httpd(m, server_pid)
        wait_deployment_prop(b, 1, "State", "Available")
        do_deployment_action(b, 1, "Update and reboot")
        wait_deployment_prop(b, 1, "State", "Failedview more...")
        server_pid = start_trivial_httpd(m)

        # Apply update
        do_deployment_action(b, 1, "Update and reboot")
        wait_deployment_prop(b, 1, "State", "Updating")

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")

        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        # After reboot, check commit
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.2")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_prop(b, 1, "Actions", "")
        wait_packages(b, 1, {"rpms-col1": [INSTALL_RPMS[0], INSTALL_RPMS[1]],
                             "rpms-col2": [INSTALL_RPMS[2]],
                             })
        wait_not_packages(b, 1, [remove_pkg])

        # Check signatures
        switch_tab(b, 1, "Signatures")
        sel = "#available-deployments > tbody:nth-child(2)"
        b.wait_in_text(sel, KEY_ID)
        b.wait_in_text(sel, "RSA")
        b.wait_in_text(sel, "Cockpit Tester <do-not-reply@cockpit-project.org>")
        b.wait_in_text(sel, "Good signature")
        b.wait_in_text(sel, "When")

        # Check rollback target
        wait_deployment_prop(b, 2, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 2, "State", "Available")
        wait_deployment_prop(b, 2, "Actions", "Roll back and reboot")
        wait_deployment_details_prop(b, 2, "Tree", "#osname", get_name(self))

        wait_packages(b, 2, {"down": [tzdata],
                             "up": [chrony],
                             "removes": ["empty-1-0.noarch"],
                             "adds": [remove_pkg],
                             })
        check_package_count(b, self.assertIn, 2)

        # Rollback
        do_deployment_action(b, 2, "Roll back and reboot")
        b.wait_visible("button:contains('Roll back')")
        wait_deployment_prop(b, 2, "State", "Updating")

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Running")

        wait_deployment_prop(b, 2, "Name", f"{get_name(self)} cockpit-base.2")
        wait_deployment_prop(b, 2, "State", "Available")
        wait_deployment_prop(b, 2, "Actions", "Roll back and reboot")

        # Only two deployments
        b.wait_not_present("#available-deployments > tbody:nth-child(4)")

        self.allow_restart_journal_messages()

    def testRebase(self):
        m = self.machine
        b = self.browser

        start_trivial_httpd(m)
        branch = m.execute(f"ostree refs --repo={REPO_LOCATION}").strip()

        # Add a new branch to the default repo
        m.execute(["ostree", "commit", f"--repo={REPO_LOCATION}",
                   "-b", "znew-branch", f"--tree=ref={branch}",
                   "--add-metadata-string", "version=branch-version"], timeout=600)
        m.execute(["ostree", "summary", f"--repo={REPO_LOCATION}", "-u"])

        rhsmcertd_hack(m)
        m.start_cockpit()
        b.login_and_go("/updates")
        b.enter_page("/updates")

        b.wait_text("#change-repo", "local")
        b.wait_in_text("#change-branch", branch)
        # open the branches menu to see the entries
        b.click("#change-branch")
        b.wait_not_in_text("#change-branch + ul li:first-child button", "error")
        b.wait_in_text("#change-branch + ul li:last-of-type", "znew-branch")
        b.call_js_func("ph_count_check", "#change-branch + ul li",  2)
        b.click("#change-branch + ul li:last-of-type button")
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", f"local:{branch}")

        b.wait_in_text("#check-for-updates-btn", "Check for updates")
        b.click("#check-for-updates-btn")

        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} branch-version")
        wait_deployment_prop(b, 1, "State", "Available")
        wait_deployment_prop(b, 1, "Actions", "Rebase and reboot")
        wait_deployment_details_prop(b, 1, "Tree", "#osname", get_name(self))
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", "local:znew-branch")

        # Apply update
        do_deployment_action(b, 1, "Rebase and reboot")
        wait_deployment_prop(b, 1, "State", "Updating")

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")

        # After reboot, check commit
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} branch-version")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", "local:znew-branch")

        self.allow_restart_journal_messages()

    def start_oci_registry(self):
        self.machine.execute(
            "podman run -d --name ostree-registry --rm -p 5000:5000 "
            "-v ostree-oci:/var/lib/registry localhost/test-registry")

    def stop_oci_registry(self):
        self.machine.execute("podman rm -ft0 ostree-registry")

    @testlib.skipImage("using image-builder workflow", "rhel4edge")
    def testOCI(self):
        b = self.browser
        m = self.machine

        branch = m.execute(f"ostree refs --repo={REPO_LOCATION}").strip()
        bash_ver = m.execute("rpm -q bash").strip()

        # start our local registry
        self.start_oci_registry()

        self.login_and_go("/updates")

        # our image defaults to local OSTree repo
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", f"local:{branch}")
        wait_packages(b, 1, {"rpms-col1": [bash_ver]})

        # rebase to our OCI repo
        # FIXME: there is currently no UI way of doing this
        m.execute("rpm-ostree rebase ostree-unverified-registry:localhost:5000/ostree-oci:cockpit1")

        # UI picks this up
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Available")
        # OCI deployments have no origin
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", "")
        wait_deployment_details_prop(b, 1, "Packages", ".same-packages",
                                     "This deployment contains the same packages as your currently booted system")

        # current deployment is now second
        wait_deployment_prop(b, 2, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 2, "State", "Running")
        wait_deployment_details_prop(b, 2, "Tree", "#osorigin", f"local:{branch}")
        wait_packages(b, 2, {"rpms-col1": [bash_ver]})

        # podman sometimes leaves the container behind after reboot despite --rm
        self.stop_oci_registry()

        # reboot into new available image (not in UI, as it's already deployed)
        m.reboot()
        self.start_oci_registry()
        self.login_and_go("/updates")

        # now the status is reversed: OCI deployment is running and has the package list
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 1, "State", "Running")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", "")
        wait_packages(b, 1, {"rpms-col1": [bash_ver]})
        # ... and second deployment is the ostree repo one
        wait_deployment_prop(b, 2, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_prop(b, 2, "State", "Available")
        wait_deployment_details_prop(b, 2, "Tree", "#osorigin", f"local:{branch}")
        wait_deployment_details_prop(b, 2, "Packages", ".same-packages",
                                     "This deployment contains the same packages as your currently booted system")


class OstreeCase(testlib.MachineCase):
    def testRemoteManagement(self):
        m = self.machine
        b = self.browser

        start_trivial_httpd(m)
        branch = m.execute(f"ostree refs --repo={REPO_LOCATION}").strip()

        rhsmcertd_hack(m)
        m.start_cockpit()
        b.login_and_go("/updates")
        b.enter_page("/updates")

        b.wait_not_present('#app .pf-v5-c-alert.pf-m-warning')
        b.wait_in_text("#change-branch", branch)
        # open the branches menu to see the entries
        b.click("#change-branch")
        b.wait_not_in_text("#change-branch + ul li:first-child button", "error")
        b.call_js_func("ph_count_check", "#change-branch + ul li",  1)
        b.wait_in_text("#change-branch + ul li", branch)
        b.click("#change-branch")
        b.wait_text("#change-repo", "local")
        b.click("#change-repo")

        b.wait_in_text(".pf-v5-c-modal-box h1", "Change repository")
        b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-simple-list .pf-m-current", "local")
        b.wait_in_text(".pf-v5-c-modal-box li:last-of-type", "Add new repository")

        b.click(".pf-v5-c-modal-box li:last-of-type button")
        b.click(".pf-v5-c-modal-box #new-gpg-verify")
        b.set_input_text(".pf-v5-c-modal-box #new-remote-url", "http://localhost:12344")
        b.set_input_text(".pf-v5-c-modal-box #new-remote-name", "zremote test")
        b.click(".pf-v5-c-modal-box #add-remote-btn")
        b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-simple-list__item-link .pf-m-danger", "Invalid remote name")
        b.set_input_text(".pf-v5-c-modal-box #new-remote-name", "zremote-test1")

        b.assert_pixels(".pf-v5-c-modal-box", "dialog-add-repository")
        b.click(".pf-v5-c-modal-box #add-remote-btn")

        b.wait_not_present(".pf-v5-c-modal-box #new-remote-name")
        b.wait_not_present(".pf-v5-c-modal-box #add-remote-btn")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button:disabled")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary")
        b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-simple-list .pf-m-current", "local")
        b.wait_in_text(".pf-v5-c-modal-box li:last-of-type button", "Add new repository")
        b.click(".pf-v5-c-modal-box #zremote-test1 a")

        b.click(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary")
        b.wait_not_present(".pf-v5-c-modal-box")

        b.wait_text("#change-repo", "zremote-test1")
        # Branch is still default
        b.wait_in_text("#change-branch", branch)
        # But can't list
        b.click("#change-branch")
        # Actual error message changes between versions
        b.wait_in_text("#change-branch + ul li button", "error: While fetching")

        # Config created
        self.assertEqual(m.execute("cat /etc/ostree/remotes.d/zremote-test1.conf").strip(),
                         '[remote "zremote-test1"]\nurl=http://localhost:12344\ngpg-verify=true')
        # No custom keyring
        self.assertFalse(m.execute("ls /sysroot/ostree/repo/zremote-test1.trustedkeys.gpg || true"))

        # Refresh goes back to default
        b.reload()
        b.enter_page("/updates")
        b.wait_text("#change-repo", "local")
        b.wait_in_text("#change-branch", branch)

        # Create a new remote with commits, just use the rpm dir
        zrepo = "/var/zrepo"
        m.execute(f"mkdir -p {zrepo}")
        m.execute("mkdir -p /tmp/rpm-data/usr/share")
        m.execute("cp -r /usr/share/rpm /tmp/rpm-data/usr/share/")
        m.execute(["ostree", "init", "--repo", zrepo, "--mode", "archive-z2"])
        m.execute(["ostree", "commit", f"--repo={zrepo}",
                   "-b", "zremote-branch1", "--orphan", "--tree=dir=/tmp/rpm-data",
                   "--add-metadata-string", "version=zremote-branch1.1"], timeout=600)
        m.execute(["ostree", "commit", f"--repo={zrepo}",
                   "-b", "zremote-branch2", "--orphan", "--tree=dir=/tmp/rpm-data",
                   "--add-metadata-string", "version=zremote-branch2.1"], timeout=600)
        start_trivial_httpd(m, remote="zremote-test1", location=zrepo)

        # Add a new branch to the default repo
        m.execute(["ostree", "commit", f"--repo={REPO_LOCATION}",
                   "-b", branch, f"--tree=ref={branch}",
                   "--add-metadata-string", "version=bad-version"], timeout=600)
        m.execute(["ostree", "summary", f"--repo={REPO_LOCATION}", "-u"])

        # Edit
        b.click("#change-repo")
        b.click(".pf-v5-c-modal-box li:contains('zremote-test1') button.pf-m-secondary.edit-remote")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button:disabled")
        b.wait_visible(".pf-v5-c-modal-box #edit-remote-url[value='http://localhost:12344']")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-form #gpg-verify:checked")
        b.wait_not_present(".pf-v5-c-modal-box #gpg-data")
        b.click(".pf-v5-c-modal-box .pf-v5-c-form button.pf-m-secondary")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-form button.pf-m-secondary")
        b.set_input_text(".pf-v5-c-modal-box #gpg-data", "bad")
        b.click(".pf-v5-c-modal-box .apply-btn")
        b.wait_visible(".pf-v5-c-modal-box div.pf-m-danger")

        with open(os.path.join(testlib.TEST_DIR, "files", "publickey.asc"), 'r') as fp:
            gpg_data = fp.read()

        b.set_val(".pf-v5-c-modal-box #gpg-data", gpg_data)
        b.wait_val(".pf-v5-c-modal-box #gpg-data", gpg_data)
        b.focus(".pf-v5-c-modal-box #gpg-data")
        b.key_press("\b")  # Backspace
        b.click(".pf-v5-c-modal-box .apply-btn")
        b.wait_not_present(".pf-v5-c-modal-box #apply-btn")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-form")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary:not(disabled)")
        m.execute("ls /sysroot/ostree/repo/zremote-test1.trustedkeys.gpg")

        b.click(".pf-v5-c-modal-box li:contains('zremote-test1') .edit-remote")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary:disabled")
        b.wait_visible(".pf-v5-c-modal-box .apply-btn")
        b.wait_visible(".pf-v5-c-modal-box #edit-remote-url[value='http://localhost:12344']")
        b.wait_visible(".pf-v5-c-modal-box #gpg-verify:checked")
        b.click(".pf-v5-c-modal-box #gpg-verify")
        b.click(".pf-v5-c-modal-box .apply-btn")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-form")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button:disabled")
        b.click(".pf-v5-c-modal-box li:contains('zremote-test1')")
        b.click(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary")
        b.wait_not_present(".pf-v5-c-modal-box")

        b.wait_text("#change-repo", "zremote-test1")
        b.wait_in_text("#change-branch", "zremote-branch1")
        b.click("#change-branch")
        b.wait_in_text("#change-branch + ul li:nth-child(1) button", "zremote-branch1")
        b.wait_in_text("#change-branch + ul li:nth-child(2) button", "zremote-branch2")
        b.call_js_func("ph_count_check", "#change-branch + ul li",  2)

        self.assertEqual(m.execute("cat /etc/ostree/remotes.d/zremote-test1.conf").strip(),
                         '[remote "zremote-test1"]\nurl=http://localhost:12344\ngpg-verify = false')

        # Check updates display
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.1")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", f"local:{branch}")

        b.click("#check-for-updates-btn")

        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} zremote-branch1.1")
        wait_deployment_prop(b, 1, "State", "Available")
        wait_deployment_prop(b, 1, "Actions", "Rebase and reboot")
        wait_deployment_details_prop(b, 1, "Tree", "#osname", get_name(self))
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", "zremote-test1:zremote-branch1")

        wait_deployment_details_prop(b, 1, "Packages", ".same-packages",
                                     "This deployment contains the same packages as your currently booted system")

        # Switching back shows pulled
        b.click("#change-branch")
        b.wait_in_text("#change-branch + ul li:first-of-type button", "zremote-branch1")
        b.click("#change-branch + ul li:first-of-type button")
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} zremote-branch1.1")

        # Refresh, back to local, pull in update
        b.reload()
        b.enter_page("/updates")
        b.wait_in_text("#change-branch", branch)
        b.click("#check-for-updates-btn")
        wait_deployment_prop(b, 1, "Actions", "Update and reboot")
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} bad-version")

        # Switching to branch shows pulled
        b.wait_text("#change-repo", "local")
        b.click("#change-repo")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-simple-list")
        b.click(".pf-v5-c-modal-box #zremote-test1 a")
        b.click(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary")
        b.wait_not_present(".pf-v5-c-modal-box")

        b.wait_text("#change-repo", "zremote-test1")
        b.wait_in_text("#change-branch", "zremote-branch1")
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} zremote-branch1.1")
        wait_deployment_prop(b, 1, "State", "Available")
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} zremote-branch1.1")

        # delete
        b.click("#change-repo")
        b.wait_in_text(".pf-v5-c-modal-box #zremote-test1", "zremote-test1")
        b.click(".pf-v5-c-modal-box #zremote-test1 .edit-remote")
        b.wait_visible(".pf-v5-c-modal-box button.pf-m-danger")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary:disabled")
        b.wait_visible(".pf-v5-c-modal-box #edit-remote-url[value='http://localhost:12344']")
        b.wait_visible("#gpg-verify")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-form #gpg-verify:not(:checked)")
        b.click(".pf-v5-c-modal-box button.pf-m-danger")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-form")

        b.wait_not_in_text(".pf-v5-c-modal-box .pf-v5-c-simple-list", "zremote-test1")
        b.wait_in_text(".pf-v5-c-modal-box .pf-v5-c-simple-list", "local")
        b.wait_not_present(".pf-v5-c-modal-box .pf-v5-c-simple-list .pf-m-current")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button:disabled")
        b.click(".pf-v5-c-modal-box #local a")
        b.wait_visible(".pf-v5-c-modal-box .pf-v5-c-simple-list .pf-m-current")
        b.click(".pf-v5-c-modal-box .pf-v5-c-modal-box__footer button.pf-m-primary")
        b.wait_not_present(".pf-v5-c-modal-box")
        b.wait_text("#change-repo", "local")
        b.wait_in_text("#change-branch", branch)
        b.click("#change-branch")
        b.wait_not_in_text("#change-branch + ul li:first-child button", "error")
        b.call_js_func("ph_count_check", "#change-branch + ul li",  1)
        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} bad-version")
        wait_deployment_prop(b, 1, "State", "Available")
        wait_deployment_details_prop(b, 1, "Tree", "#osorigin", f"local:{branch}")

    @testlib.nondestructive
    def testPermission(self):
        m = self.machine
        b = self.browser

        rhsmcertd_hack(m)

        self.login_and_go("/updates", superuser=False)
        b.wait_in_text(".pf-v5-c-empty-state__body", "Not authorized")
        self.assertIn("Reconnect", b.text(".pf-v5-c-empty-state button"))

        if self.system_before(293):
            # shell is still PF4
            b.switch_to_top()
            b.click("#super-user-indicator button")

            if m.image == "rhel4edge":  # passwordless
                b.wait_in_text(".pf-c-modal-box:contains('Administrative access')",
                               "You now have administrative access.")
                b.click(".pf-c-modal-box button:contains('Close')")
                b.wait_not_present(".pf-c-modal-box:contains('You now have administrative access.')")
            else:
                b.wait_in_text(".pf-c-modal-box:contains('Switch to administrative access')", "Password for admin:")
                b.set_input_text(".pf-c-modal-box:contains('Switch to administrative access') input", "foobar")
                b.click(".pf-c-modal-box button:contains('Authenticate')")
                b.wait_not_present(".pf-c-modal-box:contains('Switch to administrative access')")
            b.check_superuser_indicator("Administrative access")
        else:
            b.become_superuser(passwordless=m.image == "rhel4edge")
        b.switch_to_frame("cockpit1:localhost/updates")
        b.wait_visible('#available-deployments')

    def testPageStatus(self):
        m = self.machine
        b = self.browser

        rhsmcertd_hack(m)

        # preloading works, no updates available
        m.start_cockpit()
        b.login_and_go("/system")
        b.wait_text("#page_status_notification_updates", "System is up to date")
        self.assertEqual(b.attr("#page_status_notification_updates svg", "data-pficon"), "check")
        # go to updates page
        b.click("#page_status_notification_updates a")
        b.enter_page("/updates")

        # now generate an update
        remove_pkg = m.execute("rpm -qa | grep socat").strip()
        generate_new_commit(m, remove_pkg)
        m.upload(["files/publickey.asc"], "/root/")
        m.execute("ostree remote gpg-import local -k /root/publickey.asc")

        # updates page sees the new version
        b.click("#check-for-updates-btn")

        wait_deployment_prop(b, 1, "Name", f"{get_name(self)} cockpit-base.2")

        # overview page notices the new version as well
        b.go("/system")
        b.enter_page("/system")
        b.wait_in_text("#page_status_notification_updates", "Update available:")
        b.wait_in_text("#page_status_notification_updates", f"{get_name(self)} cockpit-base.2")

        # check with new session
        b.logout()
        b.login_and_go("/system")
        b.wait_in_text("#page_status_notification_updates", "Update available:")
        b.wait_in_text("#page_status_notification_updates", f"{get_name(self)} cockpit-base.2")


if __name__ == "__main__":
    testlib.test_main()
