from pytest_lazyfixture import lazy_fixture from urllib.parse import urlparse from bs4 import BeautifulSoup from flask import render_template, url_for from atlas_um.helpers.pagination import QueryPagination from atlas_um.pgdb import DNAAccountStatuses from tests.atlas_um.factories import DNAAccountFactory from tests.atlas_um.pytest_helpers import ( get_default_users_list_ordering, UrlForThisMixin, ) from . import helpers import pytest class TestListDNAAccounts(UrlForThisMixin): ENDPOINT = "dna_accounts.list" def test_title(self, app, pgdb_session, faker): search_term = faker.pystr() accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=search_term, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert page.title.get_text(strip=True) == "Users" page_head = page.find(id="subnav-header") assert page_head.get_text(strip=True) == "Users" @pytest.mark.parametrize( "authorized_user", [ lazy_fixture("authorized_admin"), lazy_fixture("authorized_manager"), ], ) def test_search_elements(self, app, faker, pgdb_session, authorized_user): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert all( helpers.added_search_form_elements(page) ), "Bad search elements" @pytest.mark.parametrize( "authorized_user", [ lazy_fixture("authorized_admin"), lazy_fixture("authorized_manager"), ], ) def test_search_form(self, app, pgdb_session, faker, authorized_user): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert all(helpers.added_search_form_elements(page)), "Bad search form" @pytest.mark.parametrize( "authorized_user", [ lazy_fixture("authorized_admin"), lazy_fixture("authorized_manager"), ], ) def test_search_term_field_empty( self, app, pgdb_session, faker, authorized_user ): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert all(helpers.added_search_form_elements(page)), "Bad search form" query_field = page.find("input", id="search-query-field") assert query_field["type"] == "search" assert query_field["name"] == "search_term" assert query_field["placeholder"] == "Search by user name or email" assert query_field.get("value") is None @pytest.mark.parametrize( "authorized_user", [ lazy_fixture("authorized_admin"), lazy_fixture("authorized_manager"), ], ) def test_search_term_field_filled( self, faker, pgdb_session, app, authorized_user ): query = faker.first_name() accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context( self.url_for_this(app, search_term=query) ): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=query, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert all(helpers.added_search_form_elements(page)), "Bad search form" query_field = page.find("input", id="search-query-field") assert query_field["type"] == "search" assert query_field["placeholder"] == "Search by user name or email" assert query_field.get("value") == query def test_new_user_button(self, app, pgdb_session, faker): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) button = page.find(id="new-user-button") expected_url = self.urlspec_for_this( app, endpoint="dna_accounts.create" ).geturl() assert button.get_text(strip=True) == "New user" assert button.attrs.get("href") == expected_url def test_export_button(self, app, pgdb_session, faker): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) button = page.find(id="export-button") expected_url = self.urlspec_for_this( app, endpoint="dna_accounts.export" ).geturl() assert button.get_text(strip=True) == "Export" assert expected_url in button.attrs.get("href") def test_accounts_table_header(self, app, pgdb_session, faker): accounts = DNAAccountFactory.build_batch(faker.pyint(max_value=5)) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) accounts_table = page.find("table", id="accounts-table") table_head = accounts_table.find("thead") headers = table_head.find_all("th") assert len(headers) == 6 assert headers[0]["class"] == ["account-first-name"] assert headers[0].get_text(strip=True) == "First Name" assert headers[0].find("a").attrs.get("href") in self.url_for_this( app, order="given_name" ) assert headers[1]["class"] == ["account-last-name"] assert headers[1].get_text(strip=True) == "Last Name" assert headers[1].find("a").attrs.get("href") in self.url_for_this( app, order="family_name" ) assert headers[2]["class"] == ["account-email"] assert headers[2].get_text(strip=True) == "Email" assert headers[2].find("a").attrs.get("href") in self.url_for_this( app, order="email_name" ) assert headers[3]["class"] == ["account-product"] assert headers[3].get_text(strip=True) == "Product" assert headers[4]["class"] == ["account-status"] assert headers[4].get_text(strip=True) == "Account Status" assert headers[5]["class"] == ["account-wip"] assert headers[5].get_text(strip=True) == "VIP" def test_accounts_table_body(self, app, pgdb_session, faker): accounts = DNAAccountFactory.build_batch( faker.pyint(min_value=1, max_value=5) ) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=None, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) accounts_table = page.find("table", id="accounts-table") rows = accounts_table.find("tbody").find_all("tr") assert len(rows) == len(accounts) for account, row in zip(accounts, rows): first_name_cell = row.find("td", class_="account-first-name") anchor = first_name_cell.find("a") assert anchor["href"] in url_for( "dna_accounts.show", account_id=account.id ) assert anchor.get_text(strip=True) == account.given_name last_name_cell = row.find("td", class_="account-last-name") anchor = last_name_cell.find("a") assert anchor["href"] in url_for( "dna_accounts.show", account_id=account.id ) assert anchor.get_text(strip=True) == account.family_name email_cell = row.find("td", class_="account-email") assert ( email_cell.get_text(strip=True) == f"{account.email}EXTERNAL" ) status_cell = row.find("td", class_="account-status") assert status_cell.get_text(strip=True) == account.status wip_cell = row.find("td", class_="account-wip") assert ( wip_cell.get_text(strip=True) == "VIP" if account.is_vip else "-" ) def test_accounts_table_body_empty(self, app, faker): query = faker.pystr() with app.test_request_context( self.url_for_this(app, search_term=query) ): page = self.render_soup( search_term=query, accounts=[], pagination=QueryPagination(), ordering=get_default_users_list_ordering([]), DNAAccountStatuses=DNAAccountStatuses, ) accounts_table = page.find("table", id="accounts-table") rows = accounts_table.find("tbody").find_all("tr") assert len(rows) == 1 row = rows[0] expected = "No results to show. Try changing search parameters." assert row.get_text(" ", strip=True) == expected header_cols = accounts_table.find("thead").find_all("th") assert int(row.find("td")["colspan"]) == len(header_cols) def test_table_summary(self, app, faker, pgdb_session): per_page = 10 req = self.url_for_this(app, per_page=per_page) user_count = faker.pyint(min_value=1, max_value=15) user_list = self.fake_user_list(user_count, pgdb_session) with app.test_request_context(req): page = self.render_soup( search_term=None, accounts=user_list, pagination=QueryPagination(total=user_count), ordering=get_default_users_list_ordering(user_list), DNAAccountStatuses=DNAAccountStatuses, ) paginator = page.find(class_="pagination") summary = paginator.find(class_="pagination-page-info") expected = f"Showing 1 - {per_page} of {user_count} results" assert summary.get_text(" ", strip=True) == expected def test_table_page_navigator(self, app, faker, pgdb_session): per_page = 10 next_page = 2 url = self.url_for_this(app, per_page=per_page) user_count = faker.pyint(min_value=1, max_value=15) user_list = self.fake_user_list(user_count, pgdb_session) with app.test_request_context(url): page = self.render_soup( search_term=None, accounts=user_list, pagination=QueryPagination(total=user_count), ordering=get_default_users_list_ordering(user_list), DNAAccountStatuses=DNAAccountStatuses, ) paginator = page.find(class_="pages-links") items = paginator.findAll("a") assert len(items) == 4 next_page_url = self.url_for_this( app, per_page=per_page, page=next_page ) # back link assert items[0].find("i", class_="angle left icon") is not None assert items[0].attrs.get("href") is None assert items[0].get_text(strip=True) == "" # first page link assert items[1].attrs.get("href") is None assert items[1].get_text(strip=True) == "1" # second page link assert items[2].attrs.get("href") in next_page_url assert items[2].get_text(strip=True) == str(next_page) # forward link assert items[3].attrs.get("href") in next_page_url assert items[3].find("i", class_="angle right icon") is not None def test_paginator_size_select(self, app, faker, pgdb_session): per_page = 10 url = self.url_for_this(app, per_page=per_page) user_count = faker.pyint(min_value=1, max_value=15) user_list = self.fake_user_list(user_count, pgdb_session) with app.test_request_context(url): page = self.render_soup( search_term=None, accounts=user_list, pagination=QueryPagination(total=user_count), ordering=get_default_users_list_ordering(user_list), DNAAccountStatuses=DNAAccountStatuses, ) page_size_selector = page.find(class_="items-on-page") assert page_size_selector is not None assert len(page_size_selector.find_all("option")) == 3 page_size_selected = page_size_selector.find("option", selected="") assert page_size_selected["value"] in url assert page_size_selected.get_text(strip=True) == "10 / Page" def test_paginator_go_to_form(self, app, faker, pgdb_session): per_page = 10 url = self.url_for_this(app, per_page=per_page) user_count = faker.pyint(min_value=1, max_value=15) user_list = self.fake_user_list(user_count, pgdb_session) with app.test_request_context(url): page = self.render_soup( search_term=None, accounts=user_list, pagination=QueryPagination(total=user_count), ordering=get_default_users_list_ordering(user_list), DNAAccountStatuses=DNAAccountStatuses, ) go_to_form = page.find("form", id="pagination-go-to") assert go_to_form.attrs.get("method").upper() == "GET" assert go_to_form.attrs.get("action") in url assert ( go_to_form.find("input", class_="goto-page", type="number") is not None ) assert ( go_to_form.find("input", attrs={"name": "page", "type": "number"}) is not None ) assert ( go_to_form.find( "input", attrs={"name": "per_page", "type": "hidden"} ) is not None ) def test_no_table_paginator_for_empty_table(self, app, faker): search_term = faker.pystr() with app.test_request_context(self.url_for_this(app)): page = self.render_soup( search_term=search_term, accounts=[], pagination=QueryPagination(), ordering=get_default_users_list_ordering([]), DNAAccountStatuses=DNAAccountStatuses, ) assert page.find(class_="table-paginator") is None def test_copyright_notice( self, app, faker, pgdb_session, expected_copyright_notice ): search_term = faker.pystr() accounts_count = faker.pyint(max_value=5) accounts = DNAAccountFactory.build_batch(accounts_count) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=search_term, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) footer = page.find("footer") copyright_notice = footer.find(id="copyright-notice") assert ( copyright_notice.get_text(strip=True) == expected_copyright_notice ) def test_frontend_libs_are_added( self, app, faker, expected_copyright_notice, pgdb_session ): search_term = faker.pystr() accounts_count = faker.pyint(max_value=5) accounts = DNAAccountFactory.build_batch(accounts_count) with app.test_request_context(self.url_for_this(app)): pgdb_session.add_all(accounts) pgdb_session.commit() page = self.render_soup( search_term=search_term, accounts=accounts, pagination=QueryPagination(), ordering=get_default_users_list_ordering(accounts), DNAAccountStatuses=DNAAccountStatuses, ) assert all(helpers.added_frontend_libs(page)) # Helpers def urlspec_for_this(self, app, **kwargs): u = urlparse(self.url_for_this(app, **kwargs)) return u._replace(scheme="", netloc="") def render_soup(self, *args, **kwargs): rt = render_template( "dna_accounts/list_dna_accounts.html", *args, **kwargs ) return BeautifulSoup(rt, "html.parser") @staticmethod def fake_user_list(qty, pgdb_session): accounts = DNAAccountFactory.build_batch(qty) for account in accounts: pgdb_session.add(account) pgdb_session.commit() return accounts