gristlabs_grist-core/sandbox/grist/test_side_effects.py
Dmitry S e2226c3ab7 (core) Store formula values in DB, and include them into .stored/.undo fields of actions.
Summary:
- Introduce a new SQLiteDB migration, which adds DB columns for formula columns
- Newly added columns have the special ['P'] (pending) value in them
  (in order to show the usual "Loading..." on the first load that triggers the migration)
- Calculated values are added to .stored/.undo fields of user actions.
- Various changes made in the sandbox to include .stored/.undo in the right order.
- OnDemand tables ignore stored formula columns, replacing them with special SQL as before
- In particular, converting to OnDemand table leaves stale values in those
  columns, we should maybe clean those out.

Some tweaks on the side:
- Allow overriding chai assertion truncateThreshold with CHAI_TRUNCATE_THRESHOLD
- Rebuild python automatically in watch mode

Test Plan: Fixed various tests, updated some fixtures. Many python tests that check actions needed adjustments because actions moved from .stored to .undo. Some checks added to catch situations previously only caught in browser tests.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2645
2020-11-04 16:45:47 -05:00

117 lines
4.5 KiB
Python

# This test verifies behavior when a formula produces side effects. The prime example is
# lookupOrAddDerived() function, which adds new records (and is the basis for summary tables).
import objtypes
import test_engine
import testutil
class TestSideEffects(test_engine.EngineTestCase):
address_table_data = [
["id", "city", "state", "amount" ],
[ 21, "New York", "NY" , 1 ],
[ 22, "Albany", "NY" , 2 ],
]
schools_table_data = [
["id", "city" , "name" ],
[1, "Boston" , "MIT" ],
[2, "New York" , "NYU" ],
]
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Address", [
[1, "city", "Text", False, "", "", ""],
[2, "state", "Text", False, "", "", ""],
[3, "amount", "Numeric", False, "", "", ""],
]],
[2, "Schools", [
[11, "name", "Text", False, "", "", ""],
[12, "city", "Text", False, "", "", ""],
]],
],
"DATA": {
"Address": address_table_data,
"Schools": schools_table_data,
}
})
def test_failure_after_side_effect(self):
# Verify that when a formula fails after a side-effect, the effect is reverted.
self.load_sample(self.sample)
formula = 'Schools.lookupOrAddDerived(city="TESTCITY")\nraise Exception("test-error")'
out_actions = self.apply_user_action(['AddColumn', 'Address', "A", { 'formula': formula }])
self.assertPartialOutActions(out_actions, { "stored": [
["AddColumn", "Address", "A", {"formula": formula, "isFormula": True, "type": "Any"}],
["AddRecord", "_grist_Tables_column", 13, {
"colId": "A", "formula": formula, "isFormula": True, "label": "A",
"parentId": 1, "parentPos": 4.0, "type": "Any", "widgetOptions": ""
}],
["BulkUpdateRecord", "Address", [21, 22], {"A": [["E", "Exception"], ["E", "Exception"]]}],
# The thing to note here is that while lookupOrAddDerived() should have added a row to
# Schools, the Exception negated it, and there is no action to add that row.
]})
# Check that data is as expected: no new records in Schools, one new column in Address.
self.assertTableData('Schools', cols="all", data=self.schools_table_data)
self.assertTableData('Address', cols="all", data=[
["id", "city", "state", "amount", "A" ],
[ 21, "New York", "NY" , 1, objtypes.RaisedException(Exception()) ],
[ 22, "Albany", "NY" , 2, objtypes.RaisedException(Exception()) ],
])
def test_calc_actions_in_side_effect_rollback(self):
self.load_sample(self.sample)
# Formula which allows a side effect to be conditionally rolled back.
formula = '''
Schools.lookupOrAddDerived(city=$city)
if $amount < 0:
raise Exception("test-error")
'''
self.add_column('Schools', 'ucity', formula='$city.upper()')
self.add_column('Address', 'A', formula=formula)
self.assertTableData('Schools', cols="all", data=[
["id", "city", "name", "ucity"],
[1, "Boston", "MIT", "BOSTON"],
[2, "New York", "NYU", "NEW YORK"],
[3, "Albany", "", "ALBANY"],
])
# Check that a successful side-effect which adds a row triggers calc actions for that row.
out_actions = self.update_record('Address', 22, city="aaa", amount=1000)
self.assertPartialOutActions(out_actions, {
"stored": [
["UpdateRecord", "Address", 22, {"amount": 1000.0, "city": "aaa"}],
["AddRecord", "Schools", 4, {"city": "aaa"}],
["UpdateRecord", "Schools", 4, {"ucity": "AAA"}],
],
})
self.assertTableData('Schools', cols="all", data=[
["id", "city", "name", "ucity"],
[1, "Boston", "MIT", "BOSTON"],
[2, "New York", "NYU", "NEW YORK"],
[3, "Albany", "", "ALBANY"],
[4, "aaa", "", "AAA"],
])
# Check that a side effect that failed and got rolled back does not include calc actions for
# the rows that didn't stay.
out_actions = self.update_record('Address', 22, city="bbb", amount=-3)
self.assertPartialOutActions(out_actions, {
"stored": [
["UpdateRecord", "Address", 22, {"amount": -3.0, "city": "bbb"}],
["UpdateRecord", "Address", 22, {"A": ["E", "Exception"]}],
],
})
self.assertTableData('Schools', cols="all", data=[
["id", "city", "name", "ucity"],
[1, "Boston", "MIT", "BOSTON"],
[2, "New York", "NYU", "NEW YORK"],
[3, "Albany", "", "ALBANY"],
[4, "aaa", "", "AAA"],
])