# -*- coding: utf-8; -*- import datetime import re from unittest.mock import patch import sqlalchemy as sa from sqlalchemy import orm from wuttjamaican.util import get_timezone_by_name from wuttaweb import diffs as mod from wuttaweb.testing import WebTestCase, VersionWebTestCase class TestWebDiff(WebTestCase): def make_diff(self, *args, **kwargs): return mod.WebDiff(self.config, *args, **kwargs) def test_render_html(self): old_data = {"foo": "bar"} new_data = {"foo": "baz"} diff = self.make_diff(old_data, new_data) html = diff.render_html() self.assertIn("", html) self.assertIn("'bar'", html) self.assertIn(f'style="background-color: {diff.old_color}"', html) self.assertIn("'baz'", html) self.assertIn(f'style="background-color: {diff.new_color}"', html) self.assertIn("", html) self.assertIn("", html) class TestVersionDiff(VersionWebTestCase): def make_diff(self, *args, **kwargs): return mod.VersionDiff(self.config, *args, **kwargs) def test_constructor(self): import sqlalchemy_continuum as continuum model = self.app.model user = model.User(username="fred") self.session.add(user) self.session.commit() user.username = "freddie" self.session.commit() self.session.delete(user) self.session.commit() txncls = continuum.transaction_class(model.User) vercls = continuum.version_class(model.User) versions = self.session.query(vercls).order_by(vercls.transaction_id).all() self.assertEqual(len(versions), 3) version = versions[0] diff = self.make_diff(version) self.assertEqual(diff.nature, "create") self.assertEqual( diff.fields, ["active", "person_uuid", "prevent_edit", "username", "uuid"], ) version = versions[1] diff = self.make_diff(version) self.assertEqual(diff.nature, "update") self.assertEqual( diff.fields, ["active", "person_uuid", "prevent_edit", "username", "uuid"], ) version = versions[2] diff = self.make_diff(version) self.assertEqual(diff.nature, "delete") self.assertEqual( diff.fields, ["active", "person_uuid", "prevent_edit", "username", "uuid"], ) def test_render_version_value_with_objref(self): import sqlalchemy_continuum as continuum model = self.app.model person = model.Person(full_name="Fred Flintstone") self.session.add(person) # create, update, delete user user = model.User(username="fred", person=person) self.session.add(user) self.session.commit() user.username = "freddie" self.session.commit() self.session.delete(user) self.session.commit() txncls = continuum.transaction_class(model.User) vercls = continuum.version_class(model.User) versions = self.session.query(vercls).order_by(vercls.transaction_id).all() self.assertEqual(len(versions), 3) # create (1st version) version = versions[0] diff = self.make_diff(version) self.assertEqual(diff.nature, "create") self.assertEqual(diff.render_old_value("username"), "") self.assertIn("fred", diff.render_new_value("username")) self.assertNotIn("freddie", diff.render_new_value("username")) self.assertEqual(diff.render_old_value("person_uuid"), "") # rendered person_uuid includes display name html = diff.render_new_value("person_uuid") self.assertIn(str(person.uuid), html) self.assertIn("Fred Flintstone", html) # update (2nd version) version = versions[1] diff = self.make_diff(version) self.assertEqual(diff.nature, "update") self.assertIn("fred", diff.render_old_value("username")) self.assertNotIn("freddie", diff.render_old_value("username")) self.assertIn("freddie", diff.render_new_value("username")) # rendered person_uuid includes display name html = diff.render_old_value("person_uuid") self.assertIn(str(person.uuid), html) self.assertIn("Fred Flintstone", html) html = diff.render_new_value("person_uuid") self.assertIn(str(person.uuid), html) self.assertIn("Fred Flintstone", html) # delete (3rd version) version = versions[2] diff = self.make_diff(version) self.assertEqual(diff.nature, "delete") self.assertIn("freddie", diff.render_old_value("username")) self.assertEqual(diff.render_new_value("username"), "") # rendered person_uuid includes display name html = diff.render_old_value("person_uuid") self.assertIn(str(person.uuid), html) self.assertIn("Fred Flintstone", html) self.assertEqual(diff.render_new_value("person_uuid"), "") def test_render_version_value_with_datetime(self): import sqlalchemy_continuum as continuum tzlocal = get_timezone_by_name("America/Los_Angeles") with patch.object(self.app, "get_timezone", return_value=tzlocal): # make one person model = self.app.model person = model.Person(full_name="Fred Flintstone") self.session.add(person) self.session.commit() # get its one version record txncls = continuum.transaction_class(model.Person) vercls = continuum.version_class(model.Person) version = self.session.query(vercls).order_by(vercls.transaction_id).one() # make a diff, but we have to mock some things up for the test # coverage, since we don't currently have a versioned datetime # field in the base model. diff = self.make_diff(version) mock_column = sa.Column("timestamp", sa.DateTime()) mock_property = orm.ColumnProperty(column=mock_column) class MockMapper: def __init__(self, mapper): self.mapper = mapper def __getattr__(self, name): if hasattr(self.mapper, name): return getattr(self.mapper, name) raise AttributeError(f"attr not found: {name}") def has_property(self, field): if field == "timestamp": return True return self.mapper.has_property(field) def get_property(self, field): if field == "timestamp": return mock_property return self.mapper.get_property(field) mock_mapper = MockMapper(diff.mapper) with patch.object(diff, "mapper", new=mock_mapper): # zone-aware local time dt = datetime.datetime(2026, 3, 14, 14, 0, tzinfo=tzlocal) html = diff.render_version_value(version, "timestamp", dt) self.assertTrue( re.search( r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}', html, ) ) self.assertTrue( re.search( r'2026-03-14 14:00-0700', html, ) ) # zone-naive (presumed UTC) dt = datetime.datetime(2026, 3, 14, 21, 0) html = diff.render_version_value(version, "timestamp", dt) self.assertTrue( re.search( r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}', html, ) ) self.assertTrue( re.search( r'2026-03-14 14:00-0700', html, ) ) # null html = diff.render_version_value(version, "timestamp", None) self.assertFalse( re.search( r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}-\d{4}', html, ) )