Plugin baselines skip tables that already exist
This commit is contained in:
@@ -276,3 +276,89 @@ def test_upgrade_all_on_fresh_db_is_clean_and_idempotent(tmp_path, monkeypatch):
|
||||
(plugin_manager._app, plugin_manager._db, plugin_manager.registry,
|
||||
plugin_manager.loader, plugin_manager.migration_manager,
|
||||
plugin_manager._registered_prefixes) = saved
|
||||
|
||||
|
||||
|
||||
# Plugins whose whole chain is a single baseline. Their tables existing already
|
||||
# is the ONLY divergence possible between schema and stamp, so upgrade-all must
|
||||
# handle it end to end. A longer chain can also meet an ALTER whose column is
|
||||
# already present, which no amount of guarding a create_table fixes - see the
|
||||
# test below for why that is a different problem.
|
||||
SINGLE_BASELINE_PLUGINS = tuple(
|
||||
plugin for plugin in POST_CUTOVER_PLUGINS
|
||||
if len(list((PLUGINS_DIR / plugin / 'migrations' / 'versions').glob('0*.py'))) == 1
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('plugin', SINGLE_BASELINE_PLUGINS)
|
||||
def test_baseline_skips_tables_that_already_exist(plugin, tmp_path, monkeypatch):
|
||||
"""A baseline must stamp, not fail, when its table is already there.
|
||||
|
||||
Reported: `flask plugin upgrade-all` failed the tools plugin with "table
|
||||
securesendsecrets already exists". Every post-cutover baseline was a plain
|
||||
op.create_table, correct only while a table's existence and its plugin's
|
||||
Alembic stamp can never disagree. They can - anything that builds tables
|
||||
from the models (db.create_all) stamps nothing, and a run that creates a
|
||||
table then fails before its stamp commits leaves the same split.
|
||||
|
||||
The other upgrade-all test DROPS these tables before running, so it only
|
||||
ever covered the state where they are absent. This is the other state.
|
||||
"""
|
||||
from sqlalchemy import inspect, text
|
||||
from shopdb.config import TestingConfig
|
||||
from shopdb import create_app
|
||||
from shopdb.extensions import db
|
||||
from shopdb.plugins import plugin_manager
|
||||
|
||||
monkeypatch.setattr(TestingConfig, 'SQLALCHEMY_DATABASE_URI',
|
||||
f'sqlite:///{tmp_path / "preexisting.db"}')
|
||||
|
||||
saved = (plugin_manager._app, plugin_manager._db, plugin_manager.registry,
|
||||
plugin_manager.loader, plugin_manager.migration_manager,
|
||||
plugin_manager._registered_prefixes)
|
||||
try:
|
||||
app = create_app('testing')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
# Leave THIS plugin's tables in place - that is the whole point -
|
||||
# and drop the other post-cutover ones so their own (longer) chains
|
||||
# run the way the sibling test already covers.
|
||||
insp0 = inspect(db.engine)
|
||||
for other in POST_CUTOVER_PLUGINS:
|
||||
if other == plugin:
|
||||
continue
|
||||
for tablename in PLUGIN_TABLE_OWNERS[other]:
|
||||
if insp0.has_table(tablename):
|
||||
db.session.execute(text(f'DROP TABLE {tablename}'))
|
||||
db.session.commit()
|
||||
|
||||
for tablename in PLUGIN_TABLE_OWNERS[plugin]:
|
||||
assert inspect(db.engine).has_table(tablename), (
|
||||
f'{tablename} should have been built by create_all; '
|
||||
f'this test is meaningless without it')
|
||||
|
||||
first = app.extensions['plugin_manager'].upgrade_all_plugins()
|
||||
assert first[plugin] == 'ok', first[plugin]
|
||||
|
||||
second = app.extensions['plugin_manager'].upgrade_all_plugins()
|
||||
assert second[plugin] == 'ok', second[plugin]
|
||||
|
||||
insp = inspect(db.engine)
|
||||
row = db.session.execute(
|
||||
text(f'SELECT version_num FROM alembic_version_{plugin}')
|
||||
).fetchone()
|
||||
assert row and row[0] == EXPECTED_HEAD_REVISION[plugin]
|
||||
|
||||
# No second index over the same columns: create_all names indexes by
|
||||
# SQLAlchemy's convention and the baseline by this repo's, so a
|
||||
# name-keyed guard would have added a duplicate rather than skipped.
|
||||
for tablename in PLUGIN_TABLE_OWNERS[plugin]:
|
||||
seen = [tuple(ix.get('column_names') or ())
|
||||
for ix in insp.get_indexes(tablename)]
|
||||
assert len(seen) == len(set(seen)), (
|
||||
f'{tablename} has two indexes over the same columns: {seen}')
|
||||
finally:
|
||||
(plugin_manager._app, plugin_manager._db, plugin_manager.registry,
|
||||
plugin_manager.loader, plugin_manager.migration_manager,
|
||||
plugin_manager._registered_prefixes) = saved
|
||||
|
||||
Reference in New Issue
Block a user