<reponame>elifesciences/elife-spectrum<filename>spectrum/input.py
"""utility library for interacting with remote services, such as:

* dashboard
* journal
* elife-bot
* journal-cms

contains no tests to be run."""

from os import path
import random
import string
import requests
from econtools import econ_workflow
from pollute import modified_environ
import mechanicalsoup
from spectrum import aws, logger
from spectrum.config import SETTINGS

LOGGER = logger.logger(__name__)

class InputBucket:
    def __init__(self, s3, bucket_name):
        self._s3 = s3
        self._bucket_name = bucket_name

    def upload(self, filename, destination_filename=None, id=None):
        if not destination_filename:
            destination_filename = path.basename(filename)
        self._s3.meta.client.upload_file(filename, self._bucket_name, destination_filename)
        LOGGER.info("Uploaded %s to %s/%s", filename, self._bucket_name, destination_filename, extra={'id': id})

    def clean(self, prefix=None):
        aws.clean_bucket(self._bucket_name, prefix)

    def name(self):
        return self._bucket_name

class Dashboard:
    def __init__(self, host, user, password):
        self._host = host
        self._user = user
        self._password = password

    def publish(self, id, version, run):
        template = "%s/api/queue_article_publication"
        url = template % self._host
        body = {'articles': [{'id': id, 'version': version, 'run': run}]}
        response = requests.post(url, auth=(self._user, self._password), json=body, verify=False)
        assert response.status_code == 200, ("Response status was %s: %s" % (response.status_code, response.text))
        LOGGER.info(
            "Pressed Publish for %s version %s run %s on dashboard",
            url,
            version,
            run,
            extra={'id': id}
        )

class BotWorkflowStarter:
    def __init__(self, aws_access_key_id, aws_secret_access_key, region_name, queue_name):
        self._aws_access_key_id = aws_access_key_id
        self._aws_secret_access_key = aws_secret_access_key
        self._region_name = region_name
        self._queue_name = queue_name

    def pubmed(self):
        LOGGER.info("Starting workflow PubmedArticleDeposit")
        with modified_environ(added={'AWS_ACCESS_KEY_ID': self._aws_access_key_id, 'AWS_SECRET_ACCESS_KEY': self._aws_secret_access_key, 'AWS_DEFAULT_REGION': self._region_name}):
            econ_workflow.start_workflow(
                self._queue_name,
                workflow_name='PubmedArticleDeposit'
            )

    def package_poa(self, filename):
        LOGGER.info("Starting workflow PackagePOA(document=%s)", filename)
        with modified_environ(added={'AWS_ACCESS_KEY_ID': self._aws_access_key_id, 'AWS_SECRET_ACCESS_KEY': self._aws_secret_access_key, 'AWS_DEFAULT_REGION': self._region_name}):
            econ_workflow.start_workflow(
                self._queue_name,
                workflow_name='PackagePOA',
                workflow_data={
                    'document': filename,
                }
            )

class JournalCms:
    def __init__(self, host, user, password):
        self._host = host
        self._user = user
        self._password = password

    def login(self):
        browser = mechanicalsoup.Browser()
        login_url = "%s/user/login" % self._host
        login_page = browser.get(login_url)
        form = mechanicalsoup.Form(login_page.soup.form)
        form.input({'name': self._user, 'pass': self._password})
        response = browser.submit(form, login_page.url)
        assert _journal_cms_page_title(response.soup) == self._user
        return JournalCmsSession(self._host, browser)

class JournalCmsSession:
    def __init__(self, host, browser):
        self._host = host
        self._browser = browser

    def create_podcast_episode(self, title, image, uri, chapter_title):
        create_url = "%s/node/add/podcast_episode" % self._host
        create_page = self._browser.get(create_url)
        form = mechanicalsoup.Form(create_page.soup.form)
        form.input({'title[0][value]': title})

        form.attach({'files[field_image_0]': image})
        LOGGER.info("Attaching image")

        form.input({'field_episode_mp3[0][uri]': uri})
        chapter_title_field = create_page.soup.form.find('input', {'name': 'field_episode_chapter[form][0][title][0][value]'})
        # Leave this condition in until after inline_entity_form is updated
        if chapter_title_field is None:
            chapter_title_field = create_page.soup.form.find('input', {'name': 'field_episode_chapter[form][inline_entity_form][title][0][value]'})
        chapter_title_field['value'] = chapter_title

        response = self._browser.submit(form, create_page.url, data={'op': 'Save'})
        # requests follows redirects by default
        _assert_html_response(response)
        assert _journal_cms_page_title(response.soup) == title

    def create_article_fragment(self, id, image):
        filtered_content_url = "%s/admin/content?status=All&type=article&title=%s" % (self._host, id)
        filtered_content_page = self._browser.get(filtered_content_url)
        assert filtered_content_page.status_code == 200, \
            "Response status of %s was: %s\nBody: %s" % (filtered_content_url, filtered_content_page.status_code, filtered_content_page.content)

        try:
            view_url = "%s%s" % (self._host, filtered_content_page.soup.find('td', 'views-field-title').find('a', href=True, text=id).get('href'))
            edit_url = "%s%s" % (self._host, filtered_content_page.soup.find('td', 'views-field-operations').find('li', 'edit').find('a', href=True, text='Edit').get('href'))
        except (AttributeError, TypeError):
            raise AssertionError('Edit link not found for article %s when loading URL %s' % (id, filtered_content_url))

        LOGGER.info(
            "Access edit form",
            extra={'id': id}
        )

        edit_page = self._browser.get(edit_url)

        form = mechanicalsoup.Form(edit_page.soup.form)

        if edit_page.soup.find('input', {'name': 'field_image_0_remove_button'}):
            self._choose_submit(form, 'field_image_0_remove_button', value='Remove')
            LOGGER.info(
                "Removing existing thumbnail",
                extra={'id': id}
            )
            response = self._browser.submit(form, edit_page.url)
            form = mechanicalsoup.Form(response.soup.form)

        form.attach({'files[field_image_0]': image})
        LOGGER.info(
            "Attaching thumbnail %s",
            image,
            extra={'id': id}
        )

        LOGGER.info(
            "Saving form",
            extra={'id': id}
        )
        # Button text will be 'Save and keep published' or 'Save and keep unpublished'
        button_text = edit_page.soup.find('div', {'id': 'edit-actions'}).find('input', 'form-submit').get('value')
        response = self._browser.submit(form, edit_page.url, data={'op': button_text})
        # requests follows redirects by default
        _assert_html_response(response)
        view_page = self._browser.get(view_url)
        img_selector = ".field--name-field-image img"
        img = view_page.soup.select_one(img_selector)
        assert img is not None, ("Cannot find %s in %s response\n%s" % (img_selector, view_page.status_code, view_page.content))
        assert "king_county" in img.get('src')
        LOGGER.info(
            "Tag: %s",
            img,
            extra={'id': id}
        )

    def _choose_submit(self, wrapped_form, name, value=None):
        """Fixed version of mechanicalsoup.Form.choose_submit()

        https://github.com/hickford/MechanicalSoup/issues/61"""

        form = wrapped_form.form

        criteria = {"name":name}
        if value:
            criteria['value'] = value
        chosen_submit = form.find("input", criteria)

        for inp in form.select("input"):
            if inp.get('type') != 'submit':
                continue
            if inp == chosen_submit:
                continue
            del inp['name']


def _assert_html_response(response):
    assert response.status_code == 200, "Response from saving the form was expected to be 200 from the listing page, but it was %s\nBody: %s" % (response.status_code, response.text)

def _journal_cms_page_title(soup):
    # <h1 class="js-quickedit-page-title title page-title"><span data-quickedit-field-id="node/1709/title/en/full" class="field field--name-title field--type-string field--label-hidden">Spectrum blog article: jvsfz4oj9vz9hk239fbpq4fbjc9yoh</span></h1>
    #<h1 class="js-quickedit-page-title title page-title">alfred</h1>
    return soup.find("h1", {"class": "page-title"}).text.strip()

class Journal:
    def __init__(self, host):
        self._host = host

    def session(self):
        browser = mechanicalsoup.Browser()
        return JournalHtmlSession(self._host, browser)

    def javascript_session(self, driver):
        return JournalJavaScriptSession(driver, self._host)


class JournalJavaScriptSession:
    ID_SUBMIT_MY_RESEARCH = 'submitResearchButton'

    def __init__(self, driver, host):
        self._driver = driver
        self._host = host

    def _log(self, message, *args, **kwargs):
        LOGGER.info(message, extra={'app':'journal'}, *args, **kwargs)

    def submit(self):
        LOGGER.info("Loading: %s", self._host)
        self._driver.get(self._host)
        selenium_title_smoke_test('eLife', self._driver)

        submit_link = self._driver.find_element_by_id(self.ID_SUBMIT_MY_RESEARCH)
        self._log("Found #%s `%s`", self.ID_SUBMIT_MY_RESEARCH, submit_link.text)
        submit_link.click()
        self._log("Clicked #%s", self.ID_SUBMIT_MY_RESEARCH)

        selenium_title_smoke_test('eLife', self._driver)
        # expand: click on login button, log in, and check final destination
        # lsh@2020-10-22: xpub removed without replacement
        #return XpubJavaScriptSession(self._driver)


class JournalHtmlSession:
    PROFILE_LINK = ".login-control__non_js_control_link"

    def __init__(self, host, browser):
        self._host = host
        self._browser = browser

    # TODO: automatically pass Referer when MechanicalSoup is upgraded to allow it
    def login(self, referer=None):
        login_url = "%s/log-in" % self._host
        headers = {}
        if referer:
            headers['Referer'] = '%s%s' % (self._host, referer)
        LOGGER.info("Logging in at %s (headers %s)", login_url, headers)
        logged_in_page = self._browser.get(login_url, headers=headers)
        # should be automatically redirected back by simulator
        LOGGER.info("Redirected to %s after log in", logged_in_page.url)
        _assert_html_response(logged_in_page)

        # if changing to another check, move in logout()
        profile = logged_in_page.soup.select_one(self.PROFILE_LINK)
        assert profile is not None, ("Cannot find %s in %s response\n%s" % (self.PROFILE_LINK, logged_in_page.status_code, logged_in_page.content))
        LOGGER.info("Found logged-in profile button at %s", self.PROFILE_LINK)

        return logged_in_page

    def logout(self):
        logout_url = "%s/log-out" % self._host
        LOGGER.info("Logging out at %s", logout_url)
        logged_out_page = self._browser.get(logout_url)
        LOGGER.info("Redirected to %s after log out", logged_out_page.url)
        _assert_html_response(logged_out_page)

        profile = logged_out_page.soup.select_one(self.PROFILE_LINK)
        assert profile is None, ("Found %s in %s response\n%s" % (self.PROFILE_LINK, logged_out_page.status_code, logged_out_page.content))

    def check(self, page_path):
        LOGGER.info("Loading page %s", page_path)
        page = self._browser.get("%s/%s" % (self._host, page_path.lstrip('/')))
        _assert_html_response(page)

        return page

class BioProtocol:
    def __init__(self, int_host, user, password):
        self.int_host = int_host
        self.user = user
        self.password = password

    def create_bioprotocol_data(self, article_id):
        "generates bioprotocol for given article data and posts it to the bioprotocol service"
        payload = [
            {
                "ProtocolSequencingNumber": "s4-1",
                "ProtocolTitle": "Protein production",
                "IsProtocol": True,
                "ProtocolStatus": 0,
                "URI": "https://en.bio-protocol.org/rap.aspx?eid=24419&item=s4-1"
            },
            {
                "ProtocolSequencingNumber": "s4-2",
                "ProtocolTitle": "Chitin-triggered alkalinization of tomato cell suspension",
                "IsProtocol": False,
                "ProtocolStatus": 0,
                "URI": "https://en.bio-protocol.org/rap.aspx?eid=24419&item=s4-2"
            }
        ]
        # http://end2end--bp.elife.internal/bioprotocol/article/123456789
        template = "%s/bioprotocol/article/%s"
        url = template % (self.int_host, article_id)
        response = requests.post(url, auth=(self.user, self.password), json=payload, verify=False)
        assert response.status_code == 200, ("Response status was %s: %s" % (response.status_code, response.text))

def invented_word(length=30, characters=None):
    if not characters:
        characters = string.ascii_lowercase + string.digits
    return ''.join(random.choice(characters) for _ in range(length))

def selenium_title_smoke_test(portion, driver):
    title = driver.title
    assert portion in title, "Title: %s\nCurrent URL: %s" % (title, driver.current_url)

PRODUCTION_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_input'])
DIGESTS_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_digests_input'])
SILENT_CORRECTION_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_silent_corrections'])
PACKAGING_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_packaging'])
POA_DELIVERY = InputBucket(aws.S3, SETTINGS['bucket_ejp_poa_delivery'])
EJP = InputBucket(aws.S3, SETTINGS['bucket_ejp_ftp'])
DASHBOARD = Dashboard(
    SETTINGS['dashboard_host'],
    SETTINGS['dashboard_user'],
    SETTINGS['dashboard_password']
)

JOURNAL_CMS = JournalCms(
    SETTINGS['journal_cms_host'],
    SETTINGS['journal_cms_user'],
    SETTINGS['journal_cms_password']
)

JOURNAL = Journal(
    SETTINGS['journal_host']
)

JOURNAL_CDN = Journal(
    SETTINGS['journal_cdn_host']
)

BOT_WORKFLOWS = BotWorkflowStarter(
    SETTINGS['aws_access_key_id'],
    SETTINGS['aws_secret_access_key'],
    SETTINGS['region_name'],
    SETTINGS['queue_workflow_starter']
)

BOT_CONFIGURATION = InputBucket(aws.S3, SETTINGS['bucket_configuration'])

BIOPROTOCOL = BioProtocol(
    SETTINGS['bioprotocol_int_host'],
    SETTINGS['bioprotocol_user'],
    SETTINGS['bioprotocol_password'])

"""utility library for interacting with remote services, such as:

* dashboard
* journal
* elife-bot
* journal-cms

contains no tests to be run."""

from os import path
import random
import string
import requests
from econtools import econ_workflow
from pollute import modified_environ
import mechanicalsoup
from spectrum import aws, logger
from spectrum.config import SETTINGS

LOGGER = logger.logger(__name__)

class InputBucket:
    def __init__(self, s3, bucket_name):
        self._s3 = s3
        self._bucket_name = bucket_name

    def upload(self, filename, destination_filename=None, id=None):
        if not destination_filename:
            destination_filename = path.basename(filename)
        self._s3.meta.client.upload_file(filename, self._bucket_name, destination_filename)
        LOGGER.info("Uploaded %s to %s/%s", filename, self._bucket_name, destination_filename, extra={'id': id})

    def clean(self, prefix=None):
        aws.clean_bucket(self._bucket_name, prefix)

    def name(self):
        return self._bucket_name

class Dashboard:
    def __init__(self, host, user, password):
        self._host = host
        self._user = user
        self._password = password

    def publish(self, id, version, run):
        template = "%s/api/queue_article_publication"
        url = template % self._host
        body = {'articles': [{'id': id, 'version': version, 'run': run}]}
        response = requests.post(url, auth=(self._user, self._password), json=body, verify=False)
        assert response.status_code == 200, ("Response status was %s: %s" % (response.status_code, response.text))
        LOGGER.info(
            "Pressed Publish for %s version %s run %s on dashboard",
            url,
            version,
            run,
            extra={'id': id}
        )

class BotWorkflowStarter:
    def __init__(self, aws_access_key_id, aws_secret_access_key, region_name, queue_name):
        self._aws_access_key_id = aws_access_key_id
        self._aws_secret_access_key = aws_secret_access_key
        self._region_name = region_name
        self._queue_name = queue_name

    def pubmed(self):
        LOGGER.info("Starting workflow PubmedArticleDeposit")
        with modified_environ(added={'AWS_ACCESS_KEY_ID': self._aws_access_key_id, 'AWS_SECRET_ACCESS_KEY': self._aws_secret_access_key, 'AWS_DEFAULT_REGION': self._region_name}):
            econ_workflow.start_workflow(
                self._queue_name,
                workflow_name='PubmedArticleDeposit'
            )

    def package_poa(self, filename):
        LOGGER.info("Starting workflow PackagePOA(document=%s)", filename)
        with modified_environ(added={'AWS_ACCESS_KEY_ID': self._aws_access_key_id, 'AWS_SECRET_ACCESS_KEY': self._aws_secret_access_key, 'AWS_DEFAULT_REGION': self._region_name}):
            econ_workflow.start_workflow(
                self._queue_name,
                workflow_name='PackagePOA',
                workflow_data={
                    'document': filename,
                }
            )

class JournalCms:
    def __init__(self, host, user, password):
        self._host = host
        self._user = user
        self._password = password

    def login(self):
        browser = mechanicalsoup.Browser()
        login_url = "%s/user/login" % self._host
        login_page = browser.get(login_url)
        form = mechanicalsoup.Form(login_page.soup.form)
        form.input({'name': self._user, 'pass': self._password})
        response = browser.submit(form, login_page.url)
        assert _journal_cms_page_title(response.soup) == self._user
        return JournalCmsSession(self._host, browser)

class JournalCmsSession:
    def __init__(self, host, browser):
        self._host = host
        self._browser = browser

    def create_podcast_episode(self, title, image, uri, chapter_title):
        create_url = "%s/node/add/podcast_episode" % self._host
        create_page = self._browser.get(create_url)
        form = mechanicalsoup.Form(create_page.soup.form)
        form.input({'title[0][value]': title})

        form.attach({'files[field_image_0]': image})
        LOGGER.info("Attaching image")

        form.input({'field_episode_mp3[0][uri]': uri})
        chapter_title_field = create_page.soup.form.find('input', {'name': 'field_episode_chapter[form][0][title][0][value]'})
        # Leave this condition in until after inline_entity_form is updated
        if chapter_title_field is None:
            chapter_title_field = create_page.soup.form.find('input', {'name': 'field_episode_chapter[form][inline_entity_form][title][0][value]'})
        chapter_title_field['value'] = chapter_title

        response = self._browser.submit(form, create_page.url, data={'op': 'Save'})
        # requests follows redirects by default
        _assert_html_response(response)
        assert _journal_cms_page_title(response.soup) == title

    def create_article_fragment(self, id, image):
        filtered_content_url = "%s/admin/content?status=All&type=article&title=%s" % (self._host, id)
        filtered_content_page = self._browser.get(filtered_content_url)
        assert filtered_content_page.status_code == 200, \
            "Response status of %s was: %s\nBody: %s" % (filtered_content_url, filtered_content_page.status_code, filtered_content_page.content)

        try:
            view_url = "%s%s" % (self._host, filtered_content_page.soup.find('td', 'views-field-title').find('a', href=True, text=id).get('href'))
            edit_url = "%s%s" % (self._host, filtered_content_page.soup.find('td', 'views-field-operations').find('li', 'edit').find('a', href=True, text='Edit').get('href'))
        except (AttributeError, TypeError):
            raise AssertionError('Edit link not found for article %s when loading URL %s' % (id, filtered_content_url))

        LOGGER.info(
            "Access edit form",
            extra={'id': id}
        )

        edit_page = self._browser.get(edit_url)

        form = mechanicalsoup.Form(edit_page.soup.form)

        if edit_page.soup.find('input', {'name': 'field_image_0_remove_button'}):
            self._choose_submit(form, 'field_image_0_remove_button', value='Remove')
            LOGGER.info(
                "Removing existing thumbnail",
                extra={'id': id}
            )
            response = self._browser.submit(form, edit_page.url)
            form = mechanicalsoup.Form(response.soup.form)

        form.attach({'files[field_image_0]': image})
        LOGGER.info(
            "Attaching thumbnail %s",
            image,
            extra={'id': id}
        )

        LOGGER.info(
            "Saving form",
            extra={'id': id}
        )
        # Button text will be 'Save and keep published' or 'Save and keep unpublished'
        button_text = edit_page.soup.find('div', {'id': 'edit-actions'}).find('input', 'form-submit').get('value')
        response = self._browser.submit(form, edit_page.url, data={'op': button_text})
        # requests follows redirects by default
        _assert_html_response(response)
        view_page = self._browser.get(view_url)
        img_selector = ".field--name-field-image img"
        img = view_page.soup.select_one(img_selector)
        assert img is not None, ("Cannot find %s in %s response\n%s" % (img_selector, view_page.status_code, view_page.content))
        assert "king_county" in img.get('src')
        LOGGER.info(
            "Tag: %s",
            img,
            extra={'id': id}
        )

    def _choose_submit(self, wrapped_form, name, value=None):
        """Fixed version of mechanicalsoup.Form.choose_submit()

        https://github.com/hickford/MechanicalSoup/issues/61"""

        form = wrapped_form.form

        criteria = {"name":name}
        if value:
            criteria['value'] = value
        chosen_submit = form.find("input", criteria)

        for inp in form.select("input"):
            if inp.get('type') != 'submit':
                continue
            if inp == chosen_submit:
                continue
            del inp['name']


def _assert_html_response(response):
    assert response.status_code == 200, "Response from saving the form was expected to be 200 from the listing page, but it was %s\nBody: %s" % (response.status_code, response.text)

def _journal_cms_page_title(soup):
    # <h1 class="js-quickedit-page-title title page-title"><span data-quickedit-field-id="node/1709/title/en/full" class="field field--name-title field--type-string field--label-hidden">Spectrum blog article: jvsfz4oj9vz9hk239fbpq4fbjc9yoh</span></h1>
    #<h1 class="js-quickedit-page-title title page-title">alfred</h1>
    return soup.find("h1", {"class": "page-title"}).text.strip()

class Journal:
    def __init__(self, host):
        self._host = host

    def session(self):
        browser = mechanicalsoup.Browser()
        return JournalHtmlSession(self._host, browser)

    def javascript_session(self, driver):
        return JournalJavaScriptSession(driver, self._host)


class JournalJavaScriptSession:
    ID_SUBMIT_MY_RESEARCH = 'submitResearchButton'

    def __init__(self, driver, host):
        self._driver = driver
        self._host = host

    def _log(self, message, *args, **kwargs):
        LOGGER.info(message, extra={'app':'journal'}, *args, **kwargs)

    def submit(self):
        LOGGER.info("Loading: %s", self._host)
        self._driver.get(self._host)
        selenium_title_smoke_test('eLife', self._driver)

        submit_link = self._driver.find_element_by_id(self.ID_SUBMIT_MY_RESEARCH)
        self._log("Found #%s `%s`", self.ID_SUBMIT_MY_RESEARCH, submit_link.text)
        submit_link.click()
        self._log("Clicked #%s", self.ID_SUBMIT_MY_RESEARCH)

        selenium_title_smoke_test('eLife', self._driver)
        # expand: click on login button, log in, and check final destination
        # lsh@2020-10-22: xpub removed without replacement
        #return XpubJavaScriptSession(self._driver)


class JournalHtmlSession:
    PROFILE_LINK = ".login-control__non_js_control_link"

    def __init__(self, host, browser):
        self._host = host
        self._browser = browser

    # TODO: automatically pass Referer when MechanicalSoup is upgraded to allow it
    def login(self, referer=None):
        login_url = "%s/log-in" % self._host
        headers = {}
        if referer:
            headers['Referer'] = '%s%s' % (self._host, referer)
        LOGGER.info("Logging in at %s (headers %s)", login_url, headers)
        logged_in_page = self._browser.get(login_url, headers=headers)
        # should be automatically redirected back by simulator
        LOGGER.info("Redirected to %s after log in", logged_in_page.url)
        _assert_html_response(logged_in_page)

        # if changing to another check, move in logout()
        profile = logged_in_page.soup.select_one(self.PROFILE_LINK)
        assert profile is not None, ("Cannot find %s in %s response\n%s" % (self.PROFILE_LINK, logged_in_page.status_code, logged_in_page.content))
        LOGGER.info("Found logged-in profile button at %s", self.PROFILE_LINK)

        return logged_in_page

    def logout(self):
        logout_url = "%s/log-out" % self._host
        LOGGER.info("Logging out at %s", logout_url)
        logged_out_page = self._browser.get(logout_url)
        LOGGER.info("Redirected to %s after log out", logged_out_page.url)
        _assert_html_response(logged_out_page)

        profile = logged_out_page.soup.select_one(self.PROFILE_LINK)
        assert profile is None, ("Found %s in %s response\n%s" % (self.PROFILE_LINK, logged_out_page.status_code, logged_out_page.content))

    def check(self, page_path):
        LOGGER.info("Loading page %s", page_path)
        page = self._browser.get("%s/%s" % (self._host, page_path.lstrip('/')))
        _assert_html_response(page)

        return page

class BioProtocol:
    def __init__(self, int_host, user, password):
        self.int_host = int_host
        self.user = user
        self.password = password

    def create_bioprotocol_data(self, article_id):
        "generates bioprotocol for given article data and posts it to the bioprotocol service"
        payload = [
            {
                "ProtocolSequencingNumber": "s4-1",
                "ProtocolTitle": "Protein production",
                "IsProtocol": True,
                "ProtocolStatus": 0,
                "URI": "https://en.bio-protocol.org/rap.aspx?eid=24419&item=s4-1"
            },
            {
                "ProtocolSequencingNumber": "s4-2",
                "ProtocolTitle": "Chitin-triggered alkalinization of tomato cell suspension",
                "IsProtocol": False,
                "ProtocolStatus": 0,
                "URI": "https://en.bio-protocol.org/rap.aspx?eid=24419&item=s4-2"
            }
        ]
        # http://end2end--bp.elife.internal/bioprotocol/article/123456789
        template = "%s/bioprotocol/article/%s"
        url = template % (self.int_host, article_id)
        response = requests.post(url, auth=(self.user, self.password), json=payload, verify=False)
        assert response.status_code == 200, ("Response status was %s: %s" % (response.status_code, response.text))

def invented_word(length=30, characters=None):
    if not characters:
        characters = string.ascii_lowercase + string.digits
    return ''.join(random.choice(characters) for _ in range(length))

def selenium_title_smoke_test(portion, driver):
    title = driver.title
    assert portion in title, "Title: %s\nCurrent URL: %s" % (title, driver.current_url)

PRODUCTION_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_input'])
DIGESTS_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_digests_input'])
SILENT_CORRECTION_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_silent_corrections'])
PACKAGING_BUCKET = InputBucket(aws.S3, SETTINGS['bucket_packaging'])
POA_DELIVERY = InputBucket(aws.S3, SETTINGS['bucket_ejp_poa_delivery'])
EJP = InputBucket(aws.S3, SETTINGS['bucket_ejp_ftp'])
DASHBOARD = Dashboard(
    SETTINGS['dashboard_host'],
    SETTINGS['dashboard_user'],
    SETTINGS['dashboard_password']
)

JOURNAL_CMS = JournalCms(
    SETTINGS['journal_cms_host'],
    SETTINGS['journal_cms_user'],
    SETTINGS['journal_cms_password']
)

JOURNAL = Journal(
    SETTINGS['journal_host']
)

JOURNAL_CDN = Journal(
    SETTINGS['journal_cdn_host']
)

BOT_WORKFLOWS = BotWorkflowStarter(
    SETTINGS['aws_access_key_id'],
    SETTINGS['aws_secret_access_key'],
    SETTINGS['region_name'],
    SETTINGS['queue_workflow_starter']
)

BOT_CONFIGURATION = InputBucket(aws.S3, SETTINGS['bucket_configuration'])

BIOPROTOCOL = BioProtocol(
    SETTINGS['bioprotocol_int_host'],
    SETTINGS['bioprotocol_user'],
    SETTINGS['bioprotocol_password'])
utility library for interacting with remote services, such as:  * dashboard * journal * elife-bot * journal-cms  contains no tests to be run. # Leave this condition in until after inline_entity_form is updated # requests follows redirects by default # Button text will be 'Save and keep published' or 'Save and keep unpublished' # requests follows redirects by default Fixed version of mechanicalsoup.Form.choose_submit()          https://github.com/hickford/MechanicalSoup/issues/61 # <h1 class="js-quickedit-page-title title page-title"><span data-quickedit-field-id="node/1709/title/en/full" class="field field--name-title field--type-string field--label-hidden">Spectrum blog article: jvsfz4oj9vz9hk239fbpq4fbjc9yoh</span></h1> #<h1 class="js-quickedit-page-title title page-title">alfred</h1> #%s `%s`", self.ID_SUBMIT_MY_RESEARCH, submit_link.text) #%s", self.ID_SUBMIT_MY_RESEARCH) # expand: click on login button, log in, and check final destination # lsh@2020-10-22: xpub removed without replacement #return XpubJavaScriptSession(self._driver) # TODO: automatically pass Referer when MechanicalSoup is upgraded to allow it # should be automatically redirected back by simulator # if changing to another check, move in logout() # http://end2end--bp.elife.internal/bioprotocol/article/123456789