1
# Copyright (C) 2013-2014 by the Free Software Foundation, Inc.
3
# This file is part of GNU Mailman.
5
# GNU Mailman is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free
7
# Software Foundation, either version 3 of the License, or (at your option)
10
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15
# You should have received a copy of the GNU General Public License along with
16
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18
"""Test database schema migrations"""
20
from __future__ import absolute_import, print_function, unicode_literals
29
import alembic.command
31
from mock import patch
32
from sqlalchemy import MetaData, Table, Column, Integer, Unicode
33
from sqlalchemy.exc import ProgrammingError, OperationalError
34
from sqlalchemy.schema import Index
36
from mailman.config import config
37
from mailman.database.alembic import alembic_cfg
38
from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
39
from mailman.database.model import Model
40
from mailman.interfaces.database import DatabaseError
41
from mailman.testing.layers import ConfigLayer
45
class TestSchemaManager(unittest.TestCase):
50
# Drop the existing database.
51
Model.metadata.drop_all(config.db.engine)
53
md.reflect(bind=config.db.engine)
54
for tablename in ('alembic_version', 'version'):
55
if tablename in md.tables:
56
md.tables[tablename].drop(config.db.engine)
57
self.schema_mgr = SchemaManager(config.db)
60
self._drop_storm_database()
61
# Restore a virgin database.
62
Model.metadata.create_all(config.db.engine)
64
def _table_exists(self, tablename):
66
md.reflect(bind=config.db.engine)
67
return tablename in md.tables
69
def _create_storm_database(self, revision):
70
version_table = Table(
71
'version', Model.metadata,
72
Column('id', Integer, primary_key=True),
73
Column('component', Unicode),
74
Column('version', Unicode),
76
version_table.create(config.db.engine)
77
config.db.store.execute(version_table.insert().values(
78
component='schema', version=revision))
80
# Other Storm specific changes, those SQL statements hopefully work on
82
config.db.engine.execute(
83
'ALTER TABLE mailinglist ADD COLUMN acceptable_aliases_id INT')
84
Index('ix_user__user_id').drop(bind=config.db.engine)
85
# Don't pollute our main metadata object, create a new one.
87
user_table = Model.metadata.tables['user'].tometadata(md)
88
Index('ix_user_user_id', user_table.c._user_id).create(
89
bind=config.db.engine)
92
def _drop_storm_database(self):
93
"""Remove the leftovers from a Storm DB.
95
A drop_all() must be issued afterwards.
97
if 'version' in Model.metadata.tables:
98
version = Model.metadata.tables['version']
99
version.drop(config.db.engine, checkfirst=True)
100
Model.metadata.remove(version)
102
Index('ix_user_user_id').drop(bind=config.db.engine)
103
except (ProgrammingError, OperationalError):
104
# Nonexistent. PostgreSQL raises a ProgrammingError, while SQLite
105
# raises an OperationalError.
109
def test_current_database(self):
110
# The database is already at the latest version.
111
alembic.command.stamp(alembic_cfg, 'head')
112
with patch('alembic.command') as alembic_command:
113
self.schema_mgr.setup_database()
114
self.assertFalse(alembic_command.stamp.called)
115
self.assertFalse(alembic_command.upgrade.called)
117
@patch('alembic.command')
118
def test_initial(self, alembic_command):
119
# No existing database.
120
self.assertFalse(self._table_exists('mailinglist'))
121
self.assertFalse(self._table_exists('alembic_version'))
122
self.schema_mgr.setup_database()
123
self.assertFalse(alembic_command.upgrade.called)
124
self.assertTrue(self._table_exists('mailinglist'))
125
self.assertTrue(self._table_exists('alembic_version'))
127
@patch('alembic.command.stamp')
128
def test_storm(self, alembic_command_stamp):
129
# Existing Storm database.
130
Model.metadata.create_all(config.db.engine)
131
self._create_storm_database(LAST_STORM_SCHEMA_VERSION)
132
self.schema_mgr.setup_database()
133
self.assertFalse(alembic_command_stamp.called)
135
self._table_exists('mailinglist')
136
and self._table_exists('alembic_version')
137
and not self._table_exists('version'))
139
@patch('alembic.command')
140
def test_old_storm(self, alembic_command):
141
# Existing Storm database in an old version.
142
Model.metadata.create_all(config.db.engine)
143
self._create_storm_database('001')
144
self.assertRaises(DatabaseError, self.schema_mgr.setup_database)
145
self.assertFalse(alembic_command.stamp.called)
146
self.assertFalse(alembic_command.upgrade.called)
148
def test_old_db(self):
149
# The database is in an old revision, must upgrade.
150
alembic.command.stamp(alembic_cfg, 'head')
152
md.reflect(bind=config.db.engine)
153
config.db.store.execute(md.tables['alembic_version'].delete())
154
config.db.store.execute(md.tables['alembic_version'].insert().values(
155
version_num='dummyrevision'))
157
with patch('alembic.command') as alembic_command:
158
self.schema_mgr.setup_database()
159
self.assertFalse(alembic_command.stamp.called)
160
self.assertTrue(alembic_command.upgrade.called)