Merge branch 'postgres-sqlalchemy2' into 'master'

Draft: temp: Sandbox with postgres and sqlalchemy2

See merge request archlinux/aurweb!778
This commit is contained in:
Mario Oenning 2023-12-09 10:23:45 +00:00
commit a25faaa87e
71 changed files with 807 additions and 743 deletions

View file

@ -60,7 +60,7 @@ other user:
GRANT ALL ON *.* TO 'user'@'localhost' WITH GRANT OPTION
The aurweb platform is intended to use the `mysql` backend, but
The aurweb platform is intended to use the `postgresql` backend, but
the `sqlite` backend is still used for sharness tests. These tests
will soon be replaced with pytest suites and `sqlite` removed.

View file

@ -44,7 +44,7 @@ from multiprocessing import Lock
import py
import pytest
from prometheus_client import values
from sqlalchemy import create_engine
from sqlalchemy import create_engine, text
from sqlalchemy.engine import URL
from sqlalchemy.engine.base import Engine
from sqlalchemy.exc import ProgrammingError
@ -52,6 +52,7 @@ from sqlalchemy.orm import scoped_session
import aurweb.config
import aurweb.db
import aurweb.schema
from aurweb import aur_logging, initdb, testing
from aurweb.testing.email import Email
from aurweb.testing.git import GitRepository
@ -68,25 +69,28 @@ values.ValueClass = values.MutexValue
def test_engine() -> Engine:
"""
Return a privileged SQLAlchemy engine with no database.
Return a privileged SQLAlchemy engine with default database.
This method is particularly useful for providing an engine that
can be used to create and drop databases from an SQL server.
:return: SQLAlchemy Engine instance (not connected to a database)
:return: SQLAlchemy Engine instance (connected to a default)
"""
unix_socket = aurweb.config.get_with_fallback("database", "socket", None)
socket = aurweb.config.get_with_fallback("database", "socket", None)
host = aurweb.config.get_with_fallback("database", "host", None)
port = aurweb.config.get_with_fallback("database", "port", None)
kwargs = {
"database": aurweb.config.get("database", "name"),
"username": aurweb.config.get("database", "user"),
"password": aurweb.config.get_with_fallback("database", "password", None),
"host": aurweb.config.get("database", "host"),
"port": aurweb.config.get_with_fallback("database", "port", None),
"query": {"unix_socket": unix_socket},
"host": socket if socket else host,
"port": port if not socket else None,
}
backend = aurweb.config.get("database", "backend")
driver = aurweb.db.DRIVERS.get(backend)
return create_engine(URL.create(driver, **kwargs))
return create_engine(URL.create(driver, **kwargs), isolation_level="AUTOCOMMIT")
class AlembicArgs:
@ -109,15 +113,16 @@ def _create_database(engine: Engine, dbname: str) -> None:
:param dbname: Database name to create
"""
conn = engine.connect()
try:
conn.execute(f"CREATE DATABASE {dbname}")
except ProgrammingError: # pragma: no cover
# The database most likely already existed if we hit
# a ProgrammingError. Just drop the database and try
# again. If at that point things still fail, any
# exception will be propogated up to the caller.
conn.execute(f"DROP DATABASE {dbname}")
conn.execute(f"CREATE DATABASE {dbname}")
with conn.begin():
try:
conn.execute(text(f"CREATE DATABASE {dbname}"))
except ProgrammingError: # pragma: no cover
# The database most likely already existed if we hit
# a ProgrammingError. Just drop the database and try
# again. If at that point things still fail, any
# exception will be propogated up to the caller.
conn.execute(text(f"DROP DATABASE {dbname} WITH (FORCE)"))
conn.execute(text(f"CREATE DATABASE {dbname}"))
conn.close()
initdb.run(AlembicArgs)
@ -131,7 +136,7 @@ def _drop_database(engine: Engine, dbname: str) -> None:
"""
aurweb.schema.metadata.drop_all(bind=engine)
conn = engine.connect()
conn.execute(f"DROP DATABASE {dbname}")
conn.execute(text(f"DROP DATABASE {dbname}"))
conn.close()
@ -178,6 +183,10 @@ def db_session(setup_database: None) -> scoped_session:
session.close()
aurweb.db.pop_session(dbname)
# Dispose engine and close connections
aurweb.db.get_engine(dbname).dispose()
aurweb.db.pop_engine(dbname)
@pytest.fixture
def db_test(db_session: scoped_session) -> None:

View file

@ -830,6 +830,7 @@ def test_post_account_edit_type_as_dev(client: TestClient, pm_user: User):
request.cookies = cookies
resp = request.post(endpoint, data=data)
assert resp.status_code == int(HTTPStatus.OK)
db.refresh(user2)
assert user2.AccountTypeID == at.DEVELOPER_ID
@ -850,6 +851,7 @@ def test_post_account_edit_invalid_type_as_pm(client: TestClient, pm_user: User)
request.cookies = cookies
resp = request.post(endpoint, data=data)
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
db.refresh(user2)
assert user2.AccountTypeID == at.USER_ID
errors = get_errors(resp.text)
@ -1020,6 +1022,7 @@ def test_post_account_edit_inactivity(client: TestClient, user: User):
assert resp.status_code == int(HTTPStatus.OK)
# Make sure the user record got updated correctly.
db.refresh(user)
assert user.InactivityTS > 0
post_data.update({"J": False})
@ -1028,6 +1031,7 @@ def test_post_account_edit_inactivity(client: TestClient, user: User):
resp = request.post(f"/account/{user.Username}/edit", data=post_data)
assert resp.status_code == int(HTTPStatus.OK)
db.refresh(user)
assert user.InactivityTS == 0
@ -1050,6 +1054,7 @@ def test_post_account_edit_suspended(client: TestClient, user: User):
assert resp.status_code == int(HTTPStatus.OK)
# Make sure the user record got updated correctly.
db.refresh(user)
assert user.Suspended
# Let's make sure the DB got updated properly.
assert user.session is None
@ -1207,6 +1212,7 @@ def test_post_account_edit_password(client: TestClient, user: User):
assert response.status_code == int(HTTPStatus.OK)
db.refresh(user)
assert user.valid_password("newPassword")
@ -1273,6 +1279,7 @@ def test_post_account_edit_self_type_as_pm(client: TestClient, pm_user: User):
resp = request.post(endpoint, data=data)
assert resp.status_code == int(HTTPStatus.OK)
db.refresh(pm_user)
assert pm_user.AccountTypeID == USER_ID
@ -1308,6 +1315,7 @@ def test_post_account_edit_other_user_type_as_pm(
assert resp.status_code == int(HTTPStatus.OK)
# Let's make sure the DB got updated properly.
db.refresh(user2)
assert user2.AccountTypeID == PACKAGE_MAINTAINER_ID
# and also that this got logged out at DEBUG level.

View file

@ -14,7 +14,7 @@ from aurweb.models.user import User
from aurweb.testing.html import get_errors
# Some test global constants.
TEST_USERNAME = "test"
TEST_USERNAME = "Test"
TEST_EMAIL = "test@example.org"
TEST_REFERER = {
"referer": aurweb.config.get("options", "aur_location") + "/login",
@ -54,36 +54,37 @@ def user() -> User:
def test_login_logout(client: TestClient, user: User):
post_data = {"user": "test", "passwd": "testPassword", "next": "/"}
for username in ["test", "TEst"]:
post_data = {"user": username, "passwd": "testPassword", "next": "/"}
with client as request:
# First, let's test get /login.
response = request.get("/login")
assert response.status_code == int(HTTPStatus.OK)
with client as request:
# First, let's test get /login.
response = request.get("/login")
assert response.status_code == int(HTTPStatus.OK)
response = request.post("/login", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
response = request.post("/login", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
# Simulate following the redirect location from above's response.
response = request.get(response.headers.get("location"))
assert response.status_code == int(HTTPStatus.OK)
# Simulate following the redirect location from above's response.
response = request.get(response.headers.get("location"))
assert response.status_code == int(HTTPStatus.OK)
response = request.post("/logout", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
response = request.post("/logout", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
request.cookies = {"AURSID": response.cookies.get("AURSID")}
response = request.post(
"/logout",
data=post_data,
)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
request.cookies = {"AURSID": response.cookies.get("AURSID")}
response = request.post(
"/logout",
data=post_data,
)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
assert "AURSID" not in response.cookies
assert "AURSID" not in response.cookies
def test_login_suspended(client: TestClient, user: User):
with db.begin():
user.Suspended = 1
user.Suspended = True
data = {"user": user.Username, "passwd": "testPassword", "next": "/"}
with client as request:
@ -184,23 +185,23 @@ def test_secure_login(getboolean: mock.Mock, client: TestClient, user: User):
def test_authenticated_login(client: TestClient, user: User):
post_data = {"user": user.Username, "passwd": "testPassword", "next": "/"}
for username in [user.Username.lower(), user.Username.upper()]:
post_data = {"user": username, "passwd": "testPassword", "next": "/"}
with client as request:
# Try to login.
response = request.post("/login", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
assert response.headers.get("location") == "/"
with client as request:
# Try to login.
request.cookies = {}
response = request.post("/login", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
assert response.headers.get("location") == "/"
# Now, let's verify that we get the logged in rendering
# when requesting GET /login as an authenticated user.
# Now, let's verify that we receive 403 Forbidden when we
# try to get /login as an authenticated user.
request.cookies = response.cookies
response = request.get("/login")
# Now, let's verify that we get the logged in rendering
# when requesting GET /login as an authenticated user.
request.cookies = response.cookies
response = request.get("/login")
assert response.status_code == int(HTTPStatus.OK)
assert "Logged-in as: <strong>test</strong>" in response.text
assert response.status_code == int(HTTPStatus.OK)
assert f"Logged-in as: <strong>{user.Username}</strong>" in response.text
def test_unauthenticated_logout_unauthorized(client: TestClient):
@ -370,5 +371,4 @@ def test_generate_unique_sid_exhausted(
assert re.search(expr, caplog.text)
assert "IntegrityError" in caplog.text
expr = r"Duplicate entry .+ for key .+SessionID.+"
assert re.search(expr, response.text)
assert "duplicate key value" in response.text

View file

@ -93,9 +93,9 @@ def make_temp_sqlite_config():
)
def make_temp_mysql_config():
def make_temp_postgres_config():
return make_temp_config(
(r"backend = .*", "backend = mysql"), (r"name = .*", "name = aurweb_test")
(r"backend = .*", "backend = postgres"), (r"name = .*", "name = aurweb_test")
)
@ -114,8 +114,8 @@ def test_sqlalchemy_sqlite_url():
aurweb.config.rehash()
def test_sqlalchemy_mysql_url():
tmpctx, tmp = make_temp_mysql_config()
def test_sqlalchemy_postgres_url():
tmpctx, tmp = make_temp_postgres_config()
with tmpctx:
with mock.patch.dict(os.environ, {"AUR_CONFIG": tmp}):
aurweb.config.rehash()
@ -123,8 +123,8 @@ def test_sqlalchemy_mysql_url():
aurweb.config.rehash()
def test_sqlalchemy_mysql_port_url():
tmpctx, tmp = make_temp_config((r";port = 3306", "port = 3306"))
def test_sqlalchemy_postgres_port_url():
tmpctx, tmp = make_temp_config((r";port = 5432", "port = 5432"))
with tmpctx:
with mock.patch.dict(os.environ, {"AUR_CONFIG": tmp}):
@ -133,7 +133,7 @@ def test_sqlalchemy_mysql_port_url():
aurweb.config.rehash()
def test_sqlalchemy_mysql_socket_url():
def test_sqlalchemy_postgres_socket_url():
tmpctx, tmp = make_temp_config()
with tmpctx:
@ -170,16 +170,6 @@ def test_connection_class_unsupported_backend():
aurweb.config.rehash()
@mock.patch("MySQLdb.connect", mock.MagicMock(return_value=True))
def test_connection_mysql():
tmpctx, tmp = make_temp_mysql_config()
with tmpctx:
with mock.patch.dict(os.environ, {"AUR_CONFIG": tmp}):
aurweb.config.rehash()
db.Connection()
aurweb.config.rehash()
def test_create_delete():
with db.begin():
account_type = db.create(AccountType, AccountType="test")
@ -212,8 +202,8 @@ def test_add_commit():
db.delete(account_type)
def test_connection_executor_mysql_paramstyle():
executor = db.ConnectionExecutor(None, backend="mysql")
def test_connection_executor_postgres_paramstyle():
executor = db.ConnectionExecutor(None, backend="postgres")
assert executor.paramstyle() == "format"

View file

@ -20,9 +20,10 @@ def test_run():
from aurweb.schema import metadata
aurweb.db.kill_engine()
metadata.drop_all(aurweb.db.get_engine())
metadata.drop_all(aurweb.db.get_engine(), checkfirst=False)
aurweb.initdb.run(Args())
# Check that constant table rows got added via initdb.
record = aurweb.db.query(AccountType, AccountType.AccountType == "User").first()
with aurweb.db.begin():
record = aurweb.db.query(AccountType, AccountType.AccountType == "User").first()
assert record is not None

View file

@ -227,7 +227,7 @@ please go to the package page [2] and select "Disable notifications".
def test_update(user: User, user2: User, pkgbases: list[PackageBase]):
pkgbase = pkgbases[0]
with db.begin():
user.UpdateNotify = 1
user.UpdateNotify = True
notif = notify.UpdateNotification(user2.ID, pkgbase.ID)
notif.send()
@ -331,7 +331,7 @@ You were removed from the co-maintainer list of {pkgbase.Name} [1].
def test_suspended_ownership_change(user: User, pkgbases: list[PackageBase]):
with db.begin():
user.Suspended = 1
user.Suspended = True
pkgbase = pkgbases[0]
notif = notify.ComaintainerAddNotification(user.ID, pkgbase.ID)
@ -491,7 +491,7 @@ def test_open_close_request_hidden_email(
# Enable the "HideEmail" option for our requester
with db.begin():
user2.HideEmail = 1
user2.HideEmail = True
# Send an open request notification.
notif = notify.RequestOpenNotification(

View file

@ -350,7 +350,7 @@ def test_pm_index_table_paging(client, pm_user):
VoteInfo,
Agenda=f"Agenda #{i}",
User=pm_user.Username,
Submitted=(ts - 5),
Submitted=(ts - 5 - i),
End=(ts + 1000),
Quorum=0.0,
Submitter=pm_user,
@ -362,7 +362,7 @@ def test_pm_index_table_paging(client, pm_user):
VoteInfo,
Agenda=f"Agenda #{25 + i}",
User=pm_user.Username,
Submitted=(ts - 1000),
Submitted=(ts - 1000 - i),
End=(ts - 5),
Quorum=0.0,
Submitter=pm_user,
@ -768,6 +768,7 @@ def test_pm_proposal_vote(client, proposal):
assert response.status_code == int(HTTPStatus.OK)
# Check that the proposal record got updated.
db.refresh(voteinfo)
assert voteinfo.Yes == yes + 1
# Check that the new PMVote exists.

View file

@ -742,14 +742,15 @@ def test_packages_empty(client: TestClient):
def test_packages_search_by_name(client: TestClient, packages: list[Package]):
with client as request:
response = request.get("/packages", params={"SeB": "n", "K": "pkg_"})
assert response.status_code == int(HTTPStatus.OK)
for keyword in ["pkg_", "PkG_"]:
with client as request:
response = request.get("/packages", params={"SeB": "n", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 50 # Default per-page
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 50 # Default per-page
def test_packages_search_by_exact_name(client: TestClient, packages: list[Package]):
@ -763,26 +764,28 @@ def test_packages_search_by_exact_name(client: TestClient, packages: list[Packag
# There is no package named exactly 'pkg_', we get 0 results.
assert len(rows) == 0
with client as request:
response = request.get("/packages", params={"SeB": "N", "K": "pkg_1"})
assert response.status_code == int(HTTPStatus.OK)
for keyword in ["pkg_1", "PkG_1"]:
with client as request:
response = request.get("/packages", params={"SeB": "N", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
# There's just one package named 'pkg_1', we get 1 result.
assert len(rows) == 1
# There's just one package named 'pkg_1', we get 1 result.
assert len(rows) == 1
def test_packages_search_by_pkgbase(client: TestClient, packages: list[Package]):
with client as request:
response = request.get("/packages", params={"SeB": "b", "K": "pkg_"})
assert response.status_code == int(HTTPStatus.OK)
for keyword in ["pkg_", "PkG_"]:
with client as request:
response = request.get("/packages", params={"SeB": "b", "K": "pkg_"})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 50
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 50
def test_packages_search_by_exact_pkgbase(client: TestClient, packages: list[Package]):
@ -794,13 +797,14 @@ def test_packages_search_by_exact_pkgbase(client: TestClient, packages: list[Pac
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 0
with client as request:
response = request.get("/packages", params={"SeB": "B", "K": "pkg_1"})
assert response.status_code == int(HTTPStatus.OK)
for keyword in ["pkg_1", "PkG_1"]:
with client as request:
response = request.get("/packages", params={"SeB": "B", "K": "pkg_1"})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
def test_packages_search_by_keywords(client: TestClient, packages: list[Package]):
@ -821,15 +825,16 @@ def test_packages_search_by_keywords(client: TestClient, packages: list[Package]
)
# And request packages with that keyword, we should get 1 result.
with client as request:
# clear fakeredis cache
cache._redis.flushall()
response = request.get("/packages", params={"SeB": "k", "K": "testKeyword"})
assert response.status_code == int(HTTPStatus.OK)
for keyword in ["testkeyword", "TestKeyWord"]:
with client as request:
# clear fakeredis cache
cache._redis.flushall()
response = request.get("/packages", params={"SeB": "k", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
# Now let's add another keyword to the same package
with db.begin():
@ -854,14 +859,13 @@ def test_packages_search_by_maintainer(
):
# We should expect that searching by `package`'s maintainer
# returns `package` in the results.
with client as request:
response = request.get(
"/packages", params={"SeB": "m", "K": maintainer.Username}
)
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
for keyword in [maintainer.Username, maintainer.Username.upper()]:
with client as request:
response = request.get("/packages", params={"SeB": "m", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
# Search again by maintainer with no keywords given.
# This kind of search returns all orphans instead.
@ -912,17 +916,16 @@ def test_packages_search_by_comaintainer(
)
# Then test that it's returned by our search.
with client as request:
# clear fakeredis cache
cache._redis.flushall()
response = request.get(
"/packages", params={"SeB": "c", "K": maintainer.Username}
)
assert response.status_code == int(HTTPStatus.OK)
for keyword in [maintainer.Username, maintainer.Username.upper()]:
with client as request:
# clear fakeredis cache
cache._redis.flushall()
response = request.get("/packages", params={"SeB": "c", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
def test_packages_search_by_co_or_maintainer(
@ -954,27 +957,27 @@ def test_packages_search_by_co_or_maintainer(
PackageComaintainer, PackageBase=package.PackageBase, User=user, Priority=1
)
with client as request:
response = request.get("/packages", params={"SeB": "M", "K": user.Username})
assert response.status_code == int(HTTPStatus.OK)
for keyword in [user.Username, user.Username.upper()]:
with client as request:
response = request.get("/packages", params={"SeB": "M", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
def test_packages_search_by_submitter(
client: TestClient, maintainer: User, package: Package
):
with client as request:
response = request.get(
"/packages", params={"SeB": "s", "K": maintainer.Username}
)
assert response.status_code == int(HTTPStatus.OK)
for keyword in [maintainer.Username, maintainer.Username.upper()]:
with client as request:
response = request.get("/packages", params={"SeB": "s", "K": keyword})
assert response.status_code == int(HTTPStatus.OK)
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
root = parse_root(response.text)
rows = root.xpath('//table[@class="results"]/tbody/tr')
assert len(rows) == 1
def test_packages_sort_by_name(client: TestClient, packages: list[Package]):
@ -1528,6 +1531,7 @@ def test_packages_post_disown_as_maintainer(
errors = get_errors(resp.text)
expected = "You did not select any packages to disown."
assert errors[0].text.strip() == expected
db.refresh(package)
assert package.PackageBase.Maintainer is not None
# Try to disown `package` without giving the confirm argument.
@ -1552,6 +1556,7 @@ def test_packages_post_disown_as_maintainer(
data={"action": "disown", "IDs": [package.ID], "confirm": True},
)
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
db.refresh(package)
assert package.PackageBase.Maintainer is not None
errors = get_errors(resp.text)
expected = "You are not allowed to disown one of the packages you selected."
@ -1565,6 +1570,7 @@ def test_packages_post_disown_as_maintainer(
data={"action": "disown", "IDs": [package.ID], "confirm": True},
)
db.get_session().expire_all()
assert package.PackageBase.Maintainer is None
successes = get_successes(resp.text)
expected = "The selected packages have been disowned."
@ -1649,6 +1655,7 @@ def test_packages_post_delete(
# Whoo. Now, let's finally make a valid request as `pm_user`
# to delete `package`.
pkgname = package.PackageBase.Name
with client as request:
request.cookies = pm_cookies
resp = request.post(
@ -1661,7 +1668,7 @@ def test_packages_post_delete(
assert successes[0].text.strip() == expected
# Expect that the package deletion was logged.
pkgbases = [package.PackageBase.Name]
pkgbases = [pkgname]
expected = (
f"Privileged user '{pm_user.Username}' deleted the "
f"following package bases: {str(pkgbases)}."

View file

@ -153,7 +153,7 @@ def test_pkg_required(package: Package):
# We want to make sure "Package" data is included
# to avoid lazy-loading the information for each dependency
qry = util.pkg_required("test", list())
assert "Packages_ID" in str(qry)
assert "packages_id" in str(qry).lower()
# We should have 1 record
assert qry.count() == 1

View file

@ -428,7 +428,7 @@ def test_pkgbase_comments(
# create notification
with db.begin():
user.CommentNotify = 1
user.CommentNotify = True
db.create(PackageNotification, PackageBase=package.PackageBase, User=user)
# post a comment
@ -688,6 +688,7 @@ def test_pkgbase_comment_pin_as_co(
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
# Assert that PinnedTS got set.
db.refresh(comment)
assert comment.PinnedTS > 0
# Unpin the comment we just pinned.
@ -698,6 +699,7 @@ def test_pkgbase_comment_pin_as_co(
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
# Let's assert that PinnedTS was unset.
db.refresh(comment)
assert comment.PinnedTS == 0
@ -716,6 +718,7 @@ def test_pkgbase_comment_pin(
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
# Assert that PinnedTS got set.
db.refresh(comment)
assert comment.PinnedTS > 0
# Unpin the comment we just pinned.
@ -726,6 +729,7 @@ def test_pkgbase_comment_pin(
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
# Let's assert that PinnedTS was unset.
db.refresh(comment)
assert comment.PinnedTS == 0
@ -1040,6 +1044,7 @@ def test_pkgbase_flag(
request.cookies = cookies
resp = request.post(endpoint, data={"comments": "Test"})
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgbase)
assert pkgbase.Flagger == user
assert pkgbase.FlaggerComment == "Test"
@ -1077,6 +1082,7 @@ def test_pkgbase_flag(
request.cookies = user2_cookies
resp = request.post(endpoint)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgbase)
assert pkgbase.Flagger == user
# Now, test that the 'maintainer' user can.
@ -1085,6 +1091,7 @@ def test_pkgbase_flag(
request.cookies = maint_cookies
resp = request.post(endpoint)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgbase)
assert pkgbase.Flagger is None
# Flag it again.
@ -1098,6 +1105,7 @@ def test_pkgbase_flag(
request.cookies = cookies
resp = request.post(endpoint)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgbase)
assert pkgbase.Flagger is None
@ -1170,6 +1178,7 @@ def test_pkgbase_vote(client: TestClient, user: User, package: Package):
vote = pkgbase.package_votes.filter(PackageVote.UsersID == user.ID).first()
assert vote is not None
db.refresh(pkgbase)
assert pkgbase.NumVotes == 1
# Remove vote.
@ -1181,6 +1190,7 @@ def test_pkgbase_vote(client: TestClient, user: User, package: Package):
vote = pkgbase.package_votes.filter(PackageVote.UsersID == user.ID).first()
assert vote is None
db.refresh(pkgbase)
assert pkgbase.NumVotes == 0
@ -1592,9 +1602,9 @@ def test_pkgbase_merge_post(
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
# Save these relationships for later comparison.
comments = package.PackageBase.comments.all()
notifs = package.PackageBase.notifications.all()
votes = package.PackageBase.package_votes.all()
comments = [row.__dict__ for row in package.PackageBase.comments.all()]
notifs = [row.__dict__ for row in package.PackageBase.notifications.all()]
votes = [row.__dict__ for row in package.PackageBase.package_votes.all()]
# Merge the package into target.
endpoint = f"/pkgbase/{package.PackageBase.Name}/merge"
@ -1612,9 +1622,13 @@ def test_pkgbase_merge_post(
# Assert that the original comments, notifs and votes we setup
# got migrated to target as intended.
assert comments == target.comments.all()
assert notifs == target.notifications.all()
assert votes == target.package_votes.all()
db.get_session().refresh(target)
assert len(comments) == target.comments.count()
assert comments[0]["PackageBaseID"] != target.ID
assert len(notifs) == target.notifications.count()
assert notifs[0]["PackageBaseID"] != target.ID
assert len(votes) == target.package_votes.count()
assert votes[0]["PackageBaseID"] != target.ID
# ...and that the package got deleted.
package = db.query(Package).filter(Package.Name == pkgname).first()

View file

@ -649,6 +649,7 @@ def test_orphan_request(
assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}"
# We should have unset the maintainer.
db.refresh(pkgbase)
assert pkgbase.Maintainer is None
# We should have removed the comaintainers.
@ -748,6 +749,7 @@ def test_orphan_as_maintainer(client: TestClient, auser: User, pkgbase: PackageB
# As the pkgbase maintainer, disowning the package just ends up
# either promoting the lowest priority comaintainer or removing
# the associated maintainer relationship altogether.
db.refresh(pkgbase)
assert pkgbase.Maintainer is None
@ -1044,6 +1046,7 @@ def test_requests_close_post(client: TestClient, user: User, pkgreq: PackageRequ
resp = request.post(f"/requests/{pkgreq.ID}/close")
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgreq)
assert pkgreq.Status == REJECTED_ID
assert pkgreq.Closer == user
assert pkgreq.ClosureComment == str()
@ -1060,6 +1063,7 @@ def test_requests_close_post_rejected(
)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(pkgreq)
assert pkgreq.Status == REJECTED_ID
assert pkgreq.Closer == user
assert pkgreq.ClosureComment == str()

View file

@ -102,6 +102,7 @@ def test_user_language(client: TestClient, user: User):
req.cookies = {"AURSID": sid}
response = req.post("/language", data=post_data)
assert response.status_code == int(HTTPStatus.SEE_OTHER)
db.refresh(user)
assert user.LangPreference == "de"

View file

@ -149,15 +149,15 @@ def assert_multiple_keys(pks):
def test_hash_query():
# No conditions
query = db.query(User)
assert util.hash_query(query) == "75e76026b7d576536e745ec22892cf8f5d7b5d62"
assert util.hash_query(query) == "ebbf077df70d97a1584f91d0dd6ec61e43aa101f"
# With where clause
query = db.query(User).filter(User.Username == "bla")
assert util.hash_query(query) == "4dca710f33b1344c27ec6a3c266970f4fa6a8a00"
assert util.hash_query(query) == "b51f2bfda67051f381a5c05b2946a1aa4d91e56d"
# With where clause and sorting
query = db.query(User).filter(User.Username == "bla").order_by(User.Username)
assert util.hash_query(query) == "ee2c7846fede430776e140f8dfe1d83cd21d2eed"
assert util.hash_query(query) == "8d458bfe1edfe8f78929fab590612e9e5d9db3a5"
# With where clause, sorting and specific columns
query = (
@ -166,4 +166,4 @@ def test_hash_query():
.order_by(User.Username)
.with_entities(User.Username)
)
assert util.hash_query(query) == "c1db751be61443d266cf643005eee7a884dac103"
assert util.hash_query(query) == "006811a386789f25d40a37496f6ac6651413c245"

View file

@ -1,5 +1,5 @@
import pytest
from sqlalchemy.exc import IntegrityError
from sqlalchemy.exc import IntegrityError, SAWarning
from aurweb import db, time
from aurweb.db import create, rollback
@ -109,7 +109,7 @@ def test_voteinfo_null_submitter_raises(user: User):
def test_voteinfo_null_agenda_raises(user: User):
with pytest.raises(IntegrityError):
with pytest.raises(IntegrityError), pytest.warns(SAWarning):
with db.begin():
create(
VoteInfo,
@ -123,7 +123,7 @@ def test_voteinfo_null_agenda_raises(user: User):
def test_voteinfo_null_user_raises(user: User):
with pytest.raises(IntegrityError):
with pytest.raises(IntegrityError), pytest.warns(SAWarning):
with db.begin():
create(
VoteInfo,
@ -137,7 +137,7 @@ def test_voteinfo_null_user_raises(user: User):
def test_voteinfo_null_submitted_raises(user: User):
with pytest.raises(IntegrityError):
with pytest.raises(IntegrityError), pytest.warns(SAWarning):
with db.begin():
create(
VoteInfo,
@ -151,7 +151,7 @@ def test_voteinfo_null_submitted_raises(user: User):
def test_voteinfo_null_end_raises(user: User):
with pytest.raises(IntegrityError):
with pytest.raises(IntegrityError), pytest.warns(SAWarning):
with db.begin():
create(
VoteInfo,