def test_latest_version_url_does_not_add_noindex_for_robots(
        test_app_client, logged_in_admin_user):
    # GIVEN the latest published version of a page with later draft created
    measure = MeasureFactory()
    measure_version = MeasureVersionFactory(measure=measure,
                                            status="APPROVED",
                                            latest=True,
                                            version="1.0")

    # WHEN we get the rendered template

    resp = test_app_client.get(
        url_for(
            "static_site.measure_version",
            topic_slug=measure_version.measure.subtopic.topic.slug,
            subtopic_slug=measure_version.measure.subtopic.slug,
            measure_slug=measure_version.measure.slug,
            version="latest",
        ))
    # THEN it should not contain a noindex tag
    assert resp.status_code == 200
    page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")
    robots_tags = page.find_all(
        "meta",
        attrs={"name": "robots"},
        content=lambda value: value and "noindex" in value)
    assert len(robots_tags) == 0
    def test_later_version_shows_links_to_earlier_versions(
            self, test_app_client, logged_in_admin_user):
        # GIVEN a page with a later published version
        measure = MeasureFactory()
        # Published version 1.0
        measure_1_0 = MeasureVersionFactory(measure=measure,
                                            status="APPROVED",
                                            latest=False,
                                            version="1.0",
                                            published_at=datetime.datetime(
                                                2018, 3, 29))
        # Published version 1.1
        measure_1_1 = MeasureVersionFactory(measure=measure,
                                            status="APPROVED",
                                            latest=False,
                                            version="1.1",
                                            published_at=datetime.datetime(
                                                2019, 3, 29))
        # Latest published version 2.0
        measure_2_0 = MeasureVersionFactory(measure=measure,
                                            status="APPROVED",
                                            latest=True,
                                            version="2.0")

        measure_1_0_url = url_for(
            "static_site.measure_version",
            topic_slug=measure_1_0.measure.subtopic.topic.slug,
            subtopic_slug=measure_1_0.measure.subtopic.slug,
            measure_slug=measure_1_0.measure.slug,
            version=measure_1_0.version,
        )
        measure_1_1_url = url_for(
            "static_site.measure_version",
            topic_slug=measure_1_1.measure.subtopic.topic.slug,
            subtopic_slug=measure_1_1.measure.subtopic.slug,
            measure_slug=measure_1_1.measure.slug,
            version=measure_1_1.version,
        )
        measure_2_0_url = url_for(
            "static_site.measure_version",
            topic_slug=measure_2_0.measure.subtopic.topic.slug,
            subtopic_slug=measure_2_0.measure.subtopic.slug,
            measure_slug=measure_2_0.measure.slug,
            version=measure_2_0.version,
        )

        # WHEN we get the latest measure page
        resp = test_app_client.get(measure_2_0_url)

        # THEN it should contain a link to the latest minor version of the earlier published version
        assert resp.status_code == 200
        page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")
        measure_1_1_links = page.find_all("a", attrs={"href": measure_1_1_url})
        assert len(measure_1_1_links) == 1
        assert measure_1_1_links[
            0].text == "Edition published on 29 March 2019"

        # AND should not contain a link to the superseded earlier version
        measure_1_0_links = page.find_all("a", attrs={"href": measure_1_0_url})
        assert len(measure_1_0_links) == 0
def test_homepage_topics_display_in_rows_with_three_columns(
        number_of_topics, row_counts, test_app_client, logged_in_rdu_user):
    for i in range(number_of_topics):
        topic = TopicFactory(slug=f"topic-{i}",
                             title=f"Test topic page #{i}",
                             short_title=f"Testing #{i}")
        subtopic = SubtopicFactory(slug=f"subtopic-{i}",
                                   title=f"Test subtopic page #{i}",
                                   topic=topic)
        measure = MeasureFactory(slug=f"measure-{i}", subtopics=[subtopic])
        MeasureVersionFactory(status="APPROVED",
                              title=f"Test measure page #{i}",
                              version="1.0",
                              measure=measure)

    resp = test_app_client.get(url_for("static_site.index"))
    assert resp.status_code == 200

    page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")
    topic_rows = page.select(".topic-row")

    assert len(topic_rows) == len(row_counts)
    for i, topic_row in enumerate(topic_rows):
        assert len(topic_rows[i].select(".topic")) == row_counts[i]

    for i in range(number_of_topics):
        assert page.select(".topic a")[i].text.strip() == f"Testing #{i}"
def test_rdu_user_cans_see_latest_draft_version(test_app_client,
                                                logged_in_rdu_user):

    measure = MeasureFactory(shared_with=[])

    MeasureVersionFactory(title="Old Test Measure Page",
                          status="APPROVED",
                          measure=measure,
                          version="1.0")
    MeasureVersionFactory(title="Updated Test Measure Page",
                          status="DRAFT",
                          measure=measure,
                          version="2.0")

    resp = test_app_client.get(
        url_for(
            "static_site.measure_version",
            topic_slug=measure.subtopic.topic.slug,
            subtopic_slug=measure.subtopic.slug,
            measure_slug=measure.slug,
            version="latest",
        ))

    assert resp.status_code == 200
    page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")
    assert page.h1.text.strip() == "Updated Test Measure Page"
 def test_get_measure_raises_if_wrong_subtopic_slug(self):
     MeasureFactory(slug="measure-slug",
                    subtopics__slug="subtopic-slug",
                    subtopics__topic__slug="topic-slug")
     with pytest.raises(PageNotFoundException):
         page_service.get_measure("topic-slug", "not-the-right-subtopic",
                                  "measure-slug")
Example #6
0
def test_data_corrections_page_shows_corrected_versions_with_link_to_page_containing_correction(
        test_app_client, logged_in_rdu_user, db_session):
    measure = MeasureFactory(slug="measure",
                             subtopics__slug="subtopic",
                             subtopics__topic__slug="topic")

    measure_versions = []
    for version, published_at, update_corrects_data_mistake in (
        ("1.0", datetime.fromisoformat("2000-01-01T12:00:00"), False),
        ("1.1", datetime.fromisoformat("2001-01-01T12:00:00"), True),
        ("1.2", datetime.fromisoformat("2002-01-01T12:00:00"), False),
        ("2.0", datetime.fromisoformat("2003-01-01T12:00:00"), False),
    ):
        measure_versions.append(
            MeasureVersionFactory(
                version=version,
                status="APPROVED",
                update_corrects_data_mistake=update_corrects_data_mistake,
                published_at=published_at,
                measure=measure,
            ))

    resp = test_app_client.get(url_for("static_site.corrections"))
    doc = html.fromstring(resp.get_data(as_text=True))

    assert len(doc.xpath("//div[@class='corrected-measure-version']")) == 1
    assert "1 January 2001" in doc.xpath(
        "//div[@class='corrected-measure-version']")[0].text_content()
    assert doc.xpath("//div[@class='corrected-measure-version']//h2/a/@href"
                     )[0] == "/topic/subtopic/measure/1.1"
def test_reference_from_subtopic_to_measures():
    # Given a subtopic with no measures
    subtopic = SubtopicFactory()
    assert subtopic.measures == []

    # When a subtopic is added to a measure
    measure1 = MeasureFactory(subtopics=[])
    measure1.subtopics.append(subtopic)

    # Then the subtopic has that measure in its measures list
    assert subtopic.measures == [measure1]

    # When another measure has the same subtopic
    measure2 = MeasureFactory(subtopics=[])
    measure2.subtopics.append(subtopic)

    # Then both measures are in the measures list
    assert subtopic.measures == [measure1, measure2]
def test_reference_from_measure_to_subtopics():
    # Given a measure with no subtopic
    measure1 = MeasureFactory(subtopics=[])
    assert measure1.subtopics == []

    # When a measure is added to a subtopic
    subtopic = SubtopicFactory()
    subtopic.measures.append(measure1)

    # Then the measure has that subtopic in its subtopics list
    assert measure1.subtopics == [subtopic]
    def test_get_latest_publishable_versions_of_measures_for_subtopic(self):
        measure = MeasureFactory()
        version_1_0 = MeasureVersionFactory(version="1.0",
                                            status="APPROVED",
                                            measure=measure)
        version_1_1 = MeasureVersionFactory(version="1.1",
                                            status="APPROVED",
                                            measure=measure)
        latest_publishable_version = MeasureVersionFactory(version="1.2",
                                                           status="APPROVED",
                                                           measure=measure)
        MeasureVersionFactory(version="1.3", status="DRAFT", measure=measure)

        measure_2 = MeasureFactory(subtopics=measure.subtopics)
        MeasureVersionFactory(version="1.0", status="DRAFT", measure=measure_2)

        measures = page_service.get_publishable_measures_for_subtopic(
            measure.subtopic)
        assert len(measures) == 1
        assert measures[0] == measure
        assert measure.versions_to_publish == [
            latest_publishable_version, version_1_1, version_1_0
        ]
        assert measure_2.versions_to_publish == []
def test_adding_an_existing_data_source(driver, app, live_server):
    rdu_user = UserFactory(user_type=TypeOfUser.RDU_USER, active=True)

    topic = TopicFactory.create(title="Police and crime")
    subtopic = SubtopicFactory.create(title="Policing", topic=topic)
    DataSourceFactory.create(title="Police statistics 2019")

    existing_measure = MeasureFactory.create(subtopics=[subtopic])
    MeasureVersionFactory.create(status="APPROVED", measure=existing_measure)

    driver_login(driver, live_server, rdu_user)
    home_page = HomePage(driver, live_server)

    home_page.click_topic_link(topic)

    topic_page = TopicPage(driver, live_server, topic)
    topic_page.expand_accordion_for_subtopic(subtopic)

    topic_page.click_add_measure(subtopic)

    create_measure_page = MeasureEditPage(driver)
    create_measure_page.set_title("Arrests")
    create_measure_page.click_save()

    create_measure_page.click_add_primary_data_source()

    fill_in(driver,
            label_text="Search for an existing data source",
            with_text="Police statistics")
    click_button_with_text(driver, "Search")

    label_for_existing_data_source = driver.find_element_by_xpath(
        "//label[text()='Police statistics 2019']")

    label_for_existing_data_source.click()

    click_button_with_text(driver, "Select")

    assert "Successfully added the data source ‘Police statistics 2019’" in driver.page_source

    click_link_with_text(driver, "Preview this version")

    assert "Police statistics 2019" in driver.page_source
    def test_version_with_factual_corrections(self, test_app_client,
                                              logged_in_rdu_user):

        # GIVEN a 1.0 measure which contains factual errors
        measure = MeasureFactory()

        MeasureVersionFactory.create(measure=measure,
                                     status="APPROVED",
                                     version="1.0")

        measure_1_1 = MeasureVersionFactory.create(
            measure=measure,
            status="APPROVED",
            version="1.1",
            update_corrects_data_mistake=True)

        measure_1_1_url = url_for(
            "static_site.measure_version",
            topic_slug=measure_1_1.measure.subtopic.topic.slug,
            subtopic_slug=measure_1_1.measure.subtopic.slug,
            measure_slug=measure_1_1.measure.slug,
            version=measure_1_1.version,
        )

        # WHEN we get the 1.1 measure page
        resp = test_app_client.get(measure_1_1_url)

        assert resp.status_code == 200

        page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")

        # THEN it should contain a banner explaining that the page contains corrections
        assert "This page corrects mistakes in a previous version. See details." in page.get_text(
        )

        see_details_link = page.find("a", string="See details")

        assert see_details_link
        assert page.select(see_details_link.get("href"))  # ID hash
def test_versioned_urls_add_noindex_for_robots(test_app_client,
                                               logged_in_admin_user):
    # GIVEN a page with a later published version
    measure = MeasureFactory()
    # Outdated version
    measure_version = MeasureVersionFactory(measure=measure,
                                            status="APPROVED",
                                            latest=True,
                                            version="1.0")

    # WHEN we get the rendered template

    resp = test_app_client.get(
        url_for(
            "static_site.measure_version",
            topic_slug=measure_version.measure.subtopic.topic.slug,
            subtopic_slug=measure_version.measure.subtopic.slug,
            measure_slug=measure_version.measure.slug,
            version="1.0",
        ))
    # THEN it should contain a noindex tag
    assert resp.status_code == 200
    page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")
    assert "noindex" == page.find("meta", attrs={"name": "robots"})["content"]
    def test_version_with_factual_error(self, test_app_client,
                                        logged_in_rdu_user):

        # GIVEN a 1.0 measure which contains factual errors
        measure = MeasureFactory()

        measure_1_0 = MeasureVersionFactory.create(measure=measure,
                                                   status="APPROVED",
                                                   version="1.0")

        MeasureVersionFactory.create(
            measure=measure,
            status="APPROVED",
            version="1.1",
            update_corrects_data_mistake=True,
            update_corrects_measure_version=measure_1_0.id,
        )

        measure_1_0_url = url_for(
            "static_site.measure_version",
            topic_slug=measure_1_0.measure.subtopic.topic.slug,
            subtopic_slug=measure_1_0.measure.subtopic.slug,
            measure_slug=measure_1_0.measure.slug,
            version=measure_1_0.version,
        )

        # WHEN we get the 1.0 measure page
        resp = test_app_client.get(measure_1_0_url)

        assert resp.status_code == 200

        page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")

        # THEN it should contain a banner explaining that the page contains an error
        assert page.find("h2", string="This page contains a factual mistake")
        assert "Details can be found on the corrected page." in page.get_text()
def test_version_history(test_app_client, logged_in_rdu_user):

    topic = TopicFactory(slug="my-topic")
    subtopic = SubtopicFactory(slug="my-subtopic", topic=topic)

    measure = MeasureFactory(slug="my-measure", subtopics=[subtopic])

    MeasureVersionFactory(
        measure=measure,
        status="APPROVED",
        version="1.0",
        published_at=datetime.datetime(2018, 1, 10),
        external_edit_summary="First published",
    )

    MeasureVersionFactory(
        measure=measure,
        status="APPROVED",
        version="1.1",
        published_at=datetime.datetime(2018, 2, 20),
        external_edit_summary="Fixed a spelling mistake.",
    )

    MeasureVersionFactory(
        measure=measure,
        status="APPROVED",
        version="1.2",
        published_at=datetime.datetime(2018, 12, 13),
        external_edit_summary="Updated headings for clarity.",
    )

    resp = test_app_client.get("/my-topic/my-subtopic/my-measure/latest")

    assert resp.status_code == 200

    page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser")

    details_tag = details_tag_with_summary(page, "full page history")

    assert details_tag

    details_text = details_tag.get_text(separator="\n", strip=True).split("\n")

    assert details_text == [
        "full page history",
        "13 December 2018",
        "Updated headings for clarity.",
        "20 February 2018",
        "Fixed a spelling mistake.",
        "10 January 2018",
        "First published",
    ]

    first_published_link = find_link_with_text(details_tag, "10 January 2018")

    assert first_published_link
    assert first_published_link.get(
        "href") == "/my-topic/my-subtopic/my-measure/1.0"

    spelling_mistake_link = find_link_with_text(details_tag,
                                                "20 February 2018")

    assert spelling_mistake_link
    assert spelling_mistake_link.get(
        "href") == "/my-topic/my-subtopic/my-measure/1.1"

    updated_headings_link = find_link_with_text(details_tag,
                                                "13 December 2018")

    assert updated_headings_link
    assert updated_headings_link.get(
        "href") == "/my-topic/my-subtopic/my-measure/1.2"
def test_static_site_build(db_session, single_use_app):
    """
    A basic test for the core flow of the static site builder. This patches/mocks a few of the key integrations to
    help prevent calling out to external services accidentally, and where possible, includes two levels of failsafes.
    1) We change the application config to not push/deploy the site
    2) We mock out the push_site, so that even if the config setting fails, this test will raise an error.

    Unfortunately, due to circular dependencies between build/build_service, it's not easy to mock out `deploy_site`.
    So we mock out the S3FileSystem, which is initialized within `deploy_site`. This will throw an error if invoked.

    `create_versioned_assets` is mocked out because that function is only needed to generate css/js, which is tested
    in a separate step outside of pytest.

    `write_html` is mocked out so that we don't need to be able to write to a filesystem.

    We should look at expanding test coverage of the static site builder eventually, but such a task should probably
    also include refactoring the site builder to be more modular, less tightly-coupled, and more easy to test.
    """
    with patch.dict(single_use_app.config):
        with patch(
                "application.sitebuilder.build.push_site") as push_site_patch:
            with patch("application.sitebuilder.build.pull_current_site"
                       ) as pull_current_site_patch:
                with patch("application.sitebuilder.build_service.S3FileSystem"
                           ) as s3_fs_patch:
                    with patch(
                            "application.dashboard.data_helpers.trello_service"
                    ) as trello_service_patch:
                        with patch(
                                "application.sitebuilder.build.create_versioned_assets"
                        ):
                            with patch(
                                    "application.sitebuilder.build.write_html"
                            ):
                                single_use_app.config["PUSH_SITE"] = False
                                single_use_app.config["DEPLOY_SITE"] = False
                                pull_current_site_patch.side_effect = UnexpectedMockInvocationException
                                push_site_patch.side_effect = UnexpectedMockInvocationException
                                s3_fs_patch.side_effect = UnexpectedMockInvocationException
                                trello_service_patch.get_measure_cards.return_value = []

                                from tests.test_data.chart_and_table import chart, simple_table

                                # Including these three versioned pages ensures the build test exercises the logic to
                                # build multiple page versions
                                measure = MeasureFactory()
                                # Outdated version
                                MeasureVersionWithDimensionFactory(
                                    measure=measure,
                                    status="APPROVED",
                                    latest=False,
                                    published_at=datetime.now().date(),
                                    version="1.0",
                                    dimensions__dimension_chart=None,
                                    dimensions__dimension_table__table_object=
                                    simple_table(),
                                )
                                # Latest published version
                                MeasureVersionWithDimensionFactory(
                                    measure=measure,
                                    status="APPROVED",
                                    latest=False,
                                    published_at=datetime.now().date(),
                                    version="2.0",
                                    dimensions__dimension_chart=None,
                                    dimensions__dimension_table__table_object=
                                    simple_table(),
                                )
                                # Newer draft version
                                MeasureVersionWithDimensionFactory(
                                    measure=measure,
                                    status="DRAFT",
                                    published_at=None,
                                    latest=True,
                                    version="2.1",
                                    dimensions__dimension_chart=None,
                                    dimensions__dimension_table=None,
                                )

                                # Publish another page with dimension, chart and table to ensure there's an item for
                                # each of the dashboard views
                                MeasureVersionWithDimensionFactory(
                                    status="APPROVED",
                                    latest=True,
                                    published_at=datetime.now().date() -
                                    timedelta(weeks=1),
                                    version="1.0",
                                    measure__subtopics__topic__slug="topic",
                                    measure__subtopics__slug="subtopic",
                                    measure__slug="measure",
                                    dimensions__guid="dimension-guid",
                                    dimensions__dimension_chart__chart_object=
                                    chart,
                                    dimensions__dimension_table__table_object=
                                    simple_table(),
                                    uploads__guid="test-download",
                                    uploads__title="Test measure page data",
                                    uploads__file_name=
                                    "test-measure-page-data.csv",
                                )

                                # Materialized views are initially empty - populate them with our fixture page data
                                refresh_materialized_views()

                                do_it(single_use_app, request_build())
 def test_get_measure_finds_measure_by_slug(self):
     measure = MeasureFactory(slug="measure-slug",
                              subtopics__slug="subtopic-slug",
                              subtopics__topic__slug="topic-slug")
     assert page_service.get_measure("topic-slug", "subtopic-slug",
                                     "measure-slug") is measure