3
0
Fork 0

feat: add basic support for merging 2 records, w/ preview

including basic logic for merging Person or User records
This commit is contained in:
Lance Edgar 2026-03-20 17:20:02 -05:00
parent 8bfbf0e570
commit ee3a789682
10 changed files with 1554 additions and 73 deletions

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8; -*-
from unittest.mock import patch
from unittest.mock import patch, MagicMock
from sqlalchemy import orm
@ -8,7 +8,7 @@ import colander
from wuttaweb.grids import Grid
from wuttaweb.views import users as mod
from wuttaweb.testing import WebTestCase, FunctionalTestCase
from wuttaweb.testing import WebTestCase, VersionWebTestCase, FunctionalTestCase
class TestUserView(WebTestCase):
@ -496,6 +496,220 @@ class TestUserView(WebTestCase):
result = view.delete_api_token()
self.assertEqual(result, {"error": "API token not found"})
def test_merge_get_simple_fields(self):
view = self.make_view()
# password field should not be included
fields = view.merge_get_simple_fields()
self.assertNotIn("password", fields)
self.assertIn("username", fields)
self.assertIn("active", fields)
def test_merge_get_additive_fields(self):
view = self.make_view()
# nb. this is not a "versioned" test case, so transaction_count
# field will not be included
fields = view.merge_get_additive_fields()
self.assertNotIn("transaction_count", fields)
self.assertIn("roles", fields)
def test_merge_get_data(self):
model = self.app.model
auth = self.app.get_auth_handler()
view = self.make_view()
admin = auth.get_role_administrator(self.session)
user = model.User(username="fred")
user.roles.append(admin)
self.session.add(user)
self.session.commit()
# nb. this is not a "versioned" test case, so transaction_count
# field will not be included
data = view.merge_get_data(user)
self.assertEqual(data["username"], "fred")
self.assertEqual(data["roles"], [admin.name])
self.assertNotIn("transaction_count", data)
def test_merge_why_not(self):
model = self.app.model
view = self.make_view()
user1 = model.User(username="freddie")
self.session.add(user1)
user2 = model.User(username="fred")
self.session.add(user2)
self.session.commit()
# normally no reason not to merge
self.assertIsNone(view.merge_why_not(user1, user2))
# can merge even if current user is involved (being kept)
with patch.object(self.request, "user", new=user2):
self.assertIsNone(view.merge_why_not(user1, user2))
# but cannot merge if it means removing current user
with patch.object(self.request, "user", new=user1):
reason = view.merge_why_not(user1, user2)
self.assertEqual(reason, "Cannot remove user who is currently logged in!")
def test_merge_execute(self):
model = self.app.model
enum = self.app.enum
auth = self.app.get_auth_handler()
view = self.make_view()
admin = auth.get_role_administrator(self.session)
user1 = model.User(username="freddie")
user1.roles.append(admin)
self.session.add(user1)
user2 = model.User(username="fred")
self.session.add(user2)
upgrade = model.Upgrade(
description="test",
created_by=user1,
executed_by=user1,
status=enum.UpgradeStatus.SUCCESS,
)
self.session.add(upgrade)
self.session.commit()
with patch.object(view, "Session", return_value=self.session):
view.merge_execute(user1, user2)
self.session.commit()
self.assertEqual(self.session.query(model.User).count(), 1)
self.assertNotIn(user1, self.session)
self.assertIn(user2, self.session)
self.assertIn(admin, user2.roles)
self.assertIs(upgrade.created_by, user2)
self.assertIs(upgrade.executed_by, user2)
class TestVersionedUserView(VersionWebTestCase):
def make_view(self):
return mod.UserView(self.request)
def test_merge_get_additive_fields(self):
view = self.make_view()
# nb. contrast this to the "non-versioned" test case above
fields = view.merge_get_additive_fields()
self.assertIn("transaction_count", fields)
self.assertIn("roles", fields)
def test_merge_get_data(self):
import sqlalchemy_continuum as continuum
model = self.app.model
auth = self.app.get_auth_handler()
txncls = continuum.transaction_class(model.User)
# nb. must reset the User model reference, due to nature of
# test setup/teardown
with patch.multiple(
mod.UserView,
model_class=model.User,
Session=MagicMock(return_value=self.session),
):
view = self.make_view()
# make admin user
admin = auth.get_role_administrator(self.session)
user = model.User(username="fred")
user.roles.append(admin)
self.session.add(user)
self.session.commit()
self.assertEqual(self.session.query(txncls).count(), 1)
# nb. contrast this to the "non-versioned" test case above
data = view.merge_get_data(user)
self.assertEqual(data["username"], "fred")
self.assertEqual(data["roles"], [admin.name])
self.assertEqual(data["transaction_count"], 0)
# admin user then creates 2 records w/ 1 txn
# nb. must trick wuttaweb continuum plugin to assign author
with patch.object(self.request, "user", new=user):
person1 = model.Person(full_name="Barney Rubble")
self.session.add(person1)
person2 = model.Person(full_name="Betty Rubble")
self.session.add(person2)
self.session.commit()
self.assertEqual(self.session.query(txncls).count(), 2)
txn1, txn2 = self.session.query(txncls).order_by(txncls.id).all()
self.assertIsNone(txn1.user)
self.assertIs(txn2.user, user)
# nb. contrast this to the "non-versioned" test case above
data = view.merge_get_data(user)
self.assertEqual(data["username"], "fred")
self.assertEqual(data["roles"], [admin.name])
self.assertEqual(data["transaction_count"], 1)
def test_merge_execute(self):
import sqlalchemy_continuum as continuum
model = self.app.model
auth = self.app.get_auth_handler()
txncls = continuum.transaction_class(model.User)
# nb. must reset the User model reference, due to nature of
# test setup/teardown
with patch.multiple(
mod.UserView,
model_class=model.User,
Session=MagicMock(return_value=self.session),
):
view = self.make_view()
# make pair of users
admin = auth.get_role_administrator(self.session)
user1 = model.User(username="freddie")
user1.roles.append(admin)
self.session.add(user1)
user2 = model.User(username="fred")
self.session.add(user2)
self.session.commit()
self.assertEqual(self.session.query(model.User).count(), 2)
self.assertEqual(self.session.query(txncls).count(), 1)
# admin user then creates 2 records w/ 1 txn
# nb. must trick wuttaweb continuum plugin to assign author
with patch.object(self.request, "user", new=user1):
person1 = model.Person(full_name="Barney Rubble")
self.session.add(person1)
person2 = model.Person(full_name="Betty Rubble")
self.session.add(person2)
self.session.commit()
self.assertEqual(self.session.query(txncls).count(), 2)
txn1, txn2 = self.session.query(txncls).order_by(txncls.id).all()
self.assertIsNone(txn1.user)
self.assertIs(txn2.user, user1)
self.assertEqual(len(user2.roles), 0)
# merge user1 => user2 (as user2, for 3rd txn)
with patch.object(self.request, "user", new=user2):
view.merge_execute(user1, user2)
self.session.commit()
self.assertEqual(self.session.query(txncls).count(), 3)
txn1, txn2, txn3 = self.session.query(txncls).order_by(txncls.id).all()
self.assertIs(txn3.user, user2)
self.assertEqual(self.session.query(model.User).count(), 1)
user = self.session.query(model.User).one()
self.assertIs(user, user2)
# user2 is now admin, and author of txn2
self.assertIn(admin, user2.roles)
self.assertIs(txn2.user, user2)
# TODO: this test seems to work fine on its own, but not in conjunction
# with the next class below. will have to sort this out before adding