~sambuddhabasu1/mailman/fix_mailman_run_error

« back to all changes in this revision

Viewing changes to src/mailman/database/tests/test_factory.py

  • Committer: Barry Warsaw
  • Date: 2014-11-01 16:49:15 UTC
  • mfrom: (7251.1.38 abhilash)
  • Revision ID: barry@list.org-20141101164915-06wqfmya6wf47n6n
Database
--------
 * The ORM layer, previously implemented with Storm, has been replaced by
   SQLAlchemy, thanks to the fantastic work by Abhilash Raj and Aurélien
   Bompard.  Alembic is now used for all database schema migrations.
 * The new logger `mailman.database` logs any errors at the database layer.

API
---
 * Several changes to the internal API:
   - `IListManager.mailing_lists` is guaranteed to be sorted in List-ID order.
   - `IDomains.mailing_lists` is guaranteed to be sorted in List-ID order.
   - Iteration over domains via the `IDomainManager` is guaranteed to be sorted
     by `IDomain.mail_host` order.
   - `ITemporaryDatabase` interface and all implementations are removed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2013-2014 by the Free Software Foundation, Inc.
 
2
#
 
3
# This file is part of GNU Mailman.
 
4
#
 
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)
 
8
# any later version.
 
9
#
 
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
 
13
# more details.
 
14
#
 
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/>.
 
17
 
 
18
"""Test database schema migrations"""
 
19
 
 
20
from __future__ import absolute_import, print_function, unicode_literals
 
21
 
 
22
__metaclass__ = type
 
23
__all__ = [
 
24
    'TestSchemaManager',
 
25
    ]
 
26
 
 
27
 
 
28
import unittest
 
29
import alembic.command
 
30
 
 
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
 
35
 
 
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
 
42
 
 
43
 
 
44
 
 
45
class TestSchemaManager(unittest.TestCase):
 
46
 
 
47
    layer = ConfigLayer
 
48
 
 
49
    def setUp(self):
 
50
        # Drop the existing database.
 
51
        Model.metadata.drop_all(config.db.engine)
 
52
        md = MetaData()
 
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)
 
58
 
 
59
    def tearDown(self):
 
60
        self._drop_storm_database()
 
61
        # Restore a virgin database.
 
62
        Model.metadata.create_all(config.db.engine)
 
63
 
 
64
    def _table_exists(self, tablename):
 
65
        md = MetaData()
 
66
        md.reflect(bind=config.db.engine)
 
67
        return tablename in md.tables
 
68
 
 
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),
 
75
            )
 
76
        version_table.create(config.db.engine)
 
77
        config.db.store.execute(version_table.insert().values(
 
78
            component='schema', version=revision))
 
79
        config.db.commit()
 
80
        # Other Storm specific changes, those SQL statements hopefully work on
 
81
        # all DB engines...
 
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.
 
86
        md = MetaData()
 
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)
 
90
        config.db.commit()
 
91
 
 
92
    def _drop_storm_database(self):
 
93
        """Remove the leftovers from a Storm DB.
 
94
 
 
95
        A drop_all() must be issued afterwards.
 
96
        """
 
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)
 
101
        try:
 
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.
 
106
            pass
 
107
        config.db.commit()
 
108
 
 
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)
 
116
 
 
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'))
 
126
 
 
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)
 
134
        self.assertTrue(
 
135
            self._table_exists('mailinglist')
 
136
            and self._table_exists('alembic_version')
 
137
            and not self._table_exists('version'))
 
138
 
 
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)
 
147
 
 
148
    def test_old_db(self):
 
149
        # The database is in an old revision, must upgrade.
 
150
        alembic.command.stamp(alembic_cfg, 'head')
 
151
        md = MetaData()
 
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'))
 
156
        config.db.commit()
 
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)