Author: astaric
Date: Fri Apr 26 09:10:14 2013
New Revision: 1476118

URL: http://svn.apache.org/r1476118
Log:
Added more tests for multiproduct upgrade, refactoring.

Modified:
    bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
    bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py

Modified: bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py?rev=1476118&r1=1476117&r2=1476118&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py (original)
+++ bloodhound/trunk/bloodhound_multiproduct/multiproduct/api.py Fri Apr 26 
09:10:14 2013
@@ -30,6 +30,7 @@ from trac.attachment import Attachment
 from trac.config import Option, PathOption
 from trac.core import Component, TracError, implements, Interface
 from trac.db import Table, Column, DatabaseManager, Index
+import trac.db_default
 from trac.env import IEnvironmentSetupParticipant, Environment
 from trac.perm import IPermissionRequestor, PermissionCache
 from trac.resource import IExternalResourceConnector, IResourceChangeListener,\
@@ -97,7 +98,7 @@ class MultiProductSystem(Component):
         global environment configuration.
         """)
 
-    SCHEMA = [mcls._get_schema() \
+    SCHEMA = [mcls._get_schema()
               for mcls in (Product, ProductResourceMap)]
 
     # Tables which should be migrated (extended with 'product' column)
@@ -202,171 +203,207 @@ class MultiProductSystem(Component):
         db_installed_version = self.get_version()
         with self.env.db_direct_transaction as db:
             if db_installed_version < 1:
-                # Initial installation
-                db("ALTER TABLE ticket ADD COLUMN product TEXT")
-                self.log.debug("creating initial db tables for %s plugin." % 
-                               PLUGIN_NAME)
-                db_connector, dummy = 
DatabaseManager(self.env)._get_connector()
-                for table in self.SCHEMA:
-                    for statement in db_connector.to_sql(table):
-                        db(statement)
+                self._add_column_product_to_ticket(db)
+                self._create_multiproduct_tables(db)
                 db_installed_version = self._update_db_version(db, 1)
 
             if db_installed_version < 2:
-                from multiproduct.model import Product
-                products = Product.select(self.env)
-                for prod in products:
-                    db("""UPDATE ticket SET product=%s
-                          WHERE product=%s""", (prod.prefix, prod.name))
+                self._replace_product_on_ticket_with_product_prefix(db)
                 db_installed_version = self._update_db_version(db, 2)
 
             if db_installed_version < 3:
-                from multiproduct.model import Product
-                import trac.db_default
-
-                def create_temp_table(table):
-                    """creates temporary table with the new schema and
-                    drops original table"""
-                    table_temp_name = '%s_temp' % table
-                    if table == 'report':
-                        cols = ','.join([c for c in table_columns[table] if c 
!= 'id'])
-                    else:
-                        cols = ','.join(table_columns[table])
-                    self.log.info("Migrating table '%s' to a new schema", 
table)
-                    db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
-                          (table_temp_name, cols, table))
-                    db("""DROP TABLE %s""" % table)
-                    db_connector, _ = 
DatabaseManager(self.env)._get_connector()
-                    table_schema = [t for t in table_defs if t.name == 
table][0]
-                    for sql in db_connector.to_sql(table_schema):
-                        db(sql)
-                    return table_temp_name, cols
-
-                def drop_temp_table(table):
-                    """drops specified temporary table"""
-                    db("""DROP TABLE %s""" % table)
-
-                TICKET_TABLES = ['ticket_change', 'ticket_custom',
-                                 'attachment',
-                                ]
                 SYSTEM_TABLES = ['system']
+                TICKET_TABLES = [
+                    'ticket_change', 'ticket_custom', 'attachment',
+                ]
+                table_defs = self._add_product_column_to_tables(
+                    self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES,
+                    db_installed_version)
+                table_columns = self._get_table_columns(table_defs)
+                create_temp_table = lambda table: self._create_temp_table(
+                    db, table, table_columns, table_defs)
+
+                self._insert_default_product(db)
+                self._upgrade_tickets(db, TICKET_TABLES, create_temp_table)
+                self._upgrade_wikis(db, create_temp_table, table_columns)
+                self._upgrade_system_tables(db, create_temp_table)
+                self._soft_link_repositories_to_default_product(db)
+                self._upgrade_table_system(SYSTEM_TABLES, create_temp_table, 
db)
+                self._enable_multiproduct_hooks()
 
-                # extend trac default schema by adding product column and 
extending key with product
-                table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
-                                                    if t.name in 
self.MIGRATE_TABLES + TICKET_TABLES + SYSTEM_TABLES]
-                for t in table_defs:
-                    t.columns.append(Column('product'))
-                    if isinstance(t.key, list):
-                        t.key = tuple(t.key) + tuple(['product'])
-                    elif isinstance(t.key, tuple):
-                        t.key = t.key + tuple(['product'])
-                    else:
-                        raise TracError("Invalid table '%s' schema key '%s' 
while upgrading "
-                                        "plugin '%s' from version %d to %d'" %
-                                        (t.name, t.key, PLUGIN_NAME, 
db_installed_version, 3))
-                table_columns = dict()
-                for table in table_defs:
-                    table_columns[table.name] = [c for c in [column.name for 
column in
-                                                                [t for t in 
table_defs if t.name == table.name][0].columns]
-                                                                    if c != 
'product']
-                # create default product
-                self.log.info("Creating default product")
-                db("""INSERT INTO bloodhound_product (prefix, name, 
description, owner)
-                      VALUES ('%s', '%s', '%s', '')""" %
-                      (DEFAULT_PRODUCT, 'Default', 'Default product'))
-
-                # fetch all products
-                all_products = Product.select(self.env)
-
-                # migrate tickets that don't have product assigned to default 
product
-                # - update ticket table product column
-                # - update ticket related tables by:
-                #   - upgrading schema
-                #   - update product column to match ticket's product
-                self.log.info("Migrating tickets w/o product to default 
product")
-                db("""UPDATE ticket SET product='%s'
-                      WHERE (product IS NULL OR product='')""" % 
DEFAULT_PRODUCT)
-                self._migrate_attachments(
-                    db("""SELECT a.type, a.id, a.filename
+                db_installed_version = self._update_db_version(db, 3)
+
+            if db_installed_version < 4:
+                self._create_product_tables_for_plugins(db)
+                db_installed_version = self._update_db_version(db, 4)
+
+            self.env.enable_multiproduct_schema(True)
+
+    def _add_column_product_to_ticket(self, db):
+        self.log.debug("Adding field product to ticket table")
+        db("ALTER TABLE ticket ADD COLUMN product TEXT")
+
+    def _create_multiproduct_tables(self, db):
+        self.log.debug("Creating initial db tables for %s plugin." %
+                       PLUGIN_NAME)
+        db_connector, dummy = DatabaseManager(self.env)._get_connector()
+        for table in self.SCHEMA:
+            for statement in db_connector.to_sql(table):
+                db(statement)
+
+    def _replace_product_on_ticket_with_product_prefix(self, db):
+        for prod in Product.select(self.env):
+            db("""UPDATE ticket SET product=%s
+                          WHERE product=%s""", (prod.prefix, prod.name))
+
+    def _create_temp_table(self, db, table, table_columns, table_defs):
+        """creates temporary table with the new schema and
+        drops original table"""
+        table_temp_name = '%s_temp' % table
+        if table == 'report':
+            cols = ','.join([c for c in table_columns[table] if c != 'id'])
+        else:
+            cols = ','.join(table_columns[table])
+        self.log.info("Migrating table '%s' to a new schema", table)
+        db("""CREATE TABLE %s AS SELECT %s FROM %s""" %
+              (table_temp_name, cols, table))
+        db("""DROP TABLE %s""" % table)
+        db_connector, _ = DatabaseManager(self.env)._get_connector()
+        table_schema = [t for t in table_defs if t.name == table][0]
+        for sql in db_connector.to_sql(table_schema):
+            db(sql)
+        return table_temp_name, cols
+
+    def _drop_temp_table(self, db, table):
+        db("""DROP TABLE %s""" % table)
+
+    def _add_product_column_to_tables(self, tables, current_version):
+        """Extend trac default schema by adding product column
+        and extending key with product.
+        """
+        table_defs = [copy.deepcopy(t) for t in trac.db_default.schema
+                      if
+                      t.name in tables]
+        for t in table_defs:
+            t.columns.append(Column('product'))
+            if isinstance(t.key, list):
+                t.key = tuple(t.key) + tuple(['product'])
+            elif isinstance(t.key, tuple):
+                t.key = t.key + tuple(['product'])
+            else:
+                raise TracError(
+                    "Invalid table '%s' schema key '%s' while upgrading "
+                    "plugin '%s' from version %d to %d'" %
+                    (t.name, t.key, PLUGIN_NAME, current_version, 3))
+        return table_defs
+
+    def _get_table_columns(self, table_defs):
+        table_columns = dict()
+        for table in table_defs:
+            table_definition = \
+                [t for t in table_defs if t.name == table.name][0]
+            column_names = \
+                [column.name for column in table_definition.columns]
+            table_columns[table.name] = \
+                [c for c in column_names if c != 'product']
+        return table_columns
+
+    def _insert_default_product(self, db):
+        self.log.info("Creating default product")
+        db("""INSERT INTO bloodhound_product (prefix, name, description, owner)
+              VALUES ('%s', '%s', '%s', '')
+           """ % (DEFAULT_PRODUCT, 'Default', 'Default product'))
+
+    def _upgrade_tickets(self, db, TICKET_TABLES, create_temp_table):
+        # migrate tickets that don't have product assigned to default product
+        # - update ticket table product column
+        # - update ticket related tables by:
+        #   - upgrading schema
+        #   - update product column to match ticket's product
+        self.log.info("Migrating tickets w/o product to default product")
+        db("""UPDATE ticket SET product='%s'
+                      WHERE (product IS NULL OR product='')
+           """ % DEFAULT_PRODUCT)
+        self._migrate_attachments(
+            db("""SELECT a.type, a.id, a.filename
                             FROM attachment a
                       INNER JOIN ticket t ON a.id = %(t.id)s
                            WHERE a.type='ticket'
-                       """ % {'t.id':  db.cast('t.id', 'text')}),
-                    to_product=DEFAULT_PRODUCT
-                )
-
-                self.log.info("Migrating ticket tables to a new schema")
-                for table in TICKET_TABLES:
-                    temp_table_name, cols = create_temp_table(table)
-                    db("""INSERT INTO %s (%s, product)
+                       """ % {'t.id': db.cast('t.id', 'text')}),
+            to_product=DEFAULT_PRODUCT
+        )
+        self.log.info("Migrating ticket tables to a new schema")
+        for table in TICKET_TABLES:
+            temp_table_name, cols = create_temp_table(table)
+            db("""INSERT INTO %s (%s, product)
                           SELECT %s, '' FROM %s""" %
-                          (table, cols, cols, temp_table_name))
-                    drop_temp_table(temp_table_name)
-
-                for table in TICKET_TABLES:
-                    if table == 'attachment':
-                        db("""
-                            UPDATE attachment
-                               SET product=(SELECT ticket.product
-                                              FROM ticket
-                                             WHERE %(ticket_id)s=attachment.id
-                                             LIMIT 1)
-                             WHERE attachment.type='ticket'
-                               AND EXISTS(SELECT ticket.product
-                                            FROM ticket
-                                           WHERE %(ticket_id)s=attachment.id)
-                           """ % dict(
-                            ticket_id=db.cast('attachment.id', 'text')))
-                    else:
-                        db("""UPDATE %s
-                              SET product=(SELECT ticket.product FROM ticket 
WHERE ticket.id=%s.ticket)""" %
-                           (table, table))
-
-                # migrate system table (except wiki which is handled 
separately) to a
-                # new schema
-                # - create tables with the new schema
-                # - populate system tables with global configuration for each 
product
-                # - exception is permission table where permissions are also 
populated in
-                #   global scope
-                self.log.info("Migrating system tables to a new schema")
-                for table in self.MIGRATE_TABLES:
-                    if table == 'wiki':
-                        continue
-                    temp_table_name, cols = create_temp_table(table)
-                    for product in all_products:
-                        self.log.info("Populating table '%s' for product '%s' 
('%s')",
-                                      table, product.name, product.prefix)
-                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM 
%s""" %
-                              (table, cols, cols, product.prefix, 
temp_table_name))
-                    if table == 'permission':
-                        self.log.info("Populating table '%s' for global 
scope", table)
-                        db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM 
%s""" %
-                              (table, cols, cols, '', temp_table_name))
-                    drop_temp_table(temp_table_name)
-
-                # migrate wiki table
-                # - populate system wikis to all products + global scope
-                # - update wiki attachment product to match wiki product
-                table = 'wiki'
-                temp_table_name, cols = create_temp_table(table)
-                self.log.info("Migrating wikis to global context")
-                db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
-                   (table, cols, cols, temp_table_name))
+               (table, cols, cols, temp_table_name))
+            self._drop_temp_table(db, temp_table_name)
+            if table == 'attachment':
                 db("""UPDATE attachment
-                         SET product=''
-                       WHERE attachment.type='wiki'""")
-
-                for wiki_name, wiki_version, wiki_product in db("""
+                         SET product=(SELECT ticket.product
+                                        FROM ticket
+                                       WHERE %(ticket.id)s=attachment.id
+                                       LIMIT 1)
+                       WHERE attachment.type='ticket'
+                         AND EXISTS(SELECT ticket.product
+                                      FROM ticket
+                                     WHERE %(ticket.id)s=attachment.id)
+                   """ % {'ticket.id': db.cast('ticket.id', 'text')})
+            else:
+                db("""UPDATE %(table)s
+                         SET product=(SELECT ticket.product
+                                        FROM ticket
+                                       WHERE ticket.id=%(table)s.ticket)
+                   """ % {'table': table})
+
+    def _upgrade_system_tables(self, db, create_temp_table):
+        # migrate system table (except wiki which is handled separately)
+        # to a new schema
+        # - create tables with the new schema
+        # - populate system tables with global configuration for each product
+        # - exception is permission table where permissions
+        #   are also populated in global scope
+        self.log.info("Migrating system tables to a new schema")
+        for table in self.MIGRATE_TABLES:
+            if table == 'wiki':
+                continue
+            temp_table_name, cols = create_temp_table(table)
+            for product in Product.select(self.env):
+                self.log.info("Populating table '%s' for product '%s' ('%s')",
+                              table, product.name, product.prefix)
+                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM %s""" %
+                   (table, cols, cols, product.prefix, temp_table_name))
+            if table == 'permission':
+                self.log.info("Populating table '%s' for global scope", table)
+                db("""INSERT INTO %s (%s, product) SELECT %s,'%s' FROM %s""" %
+                   (table, cols, cols, '', temp_table_name))
+            self._drop_temp_table(db, temp_table_name)
+
+    def _upgrade_wikis(self, db, create_temp_table,
+                       table_columns):
+        # migrate wiki table
+        # - populate system wikis to all products + global scope
+        # - update wiki attachment product to match wiki product
+        table = 'wiki'
+        temp_table_name, cols = create_temp_table(table)
+        self.log.info("Migrating wikis to global context")
+        db("""INSERT INTO %s (%s, product) SELECT %s, '' FROM %s""" %
+           (table, cols, cols, temp_table_name))
+        db("""UPDATE attachment
+                 SET product=''
+               WHERE attachment.type='wiki'""")
+        for wiki_name, wiki_version, wiki_product in db("""
                         SELECT name, version, product FROM %s""" % table):
-                    attachment_cols = ','.join(table_columns['attachment'])
-                    if wiki_name in self.system_wiki_list:
-                        for product in all_products:
-                            db("""INSERT INTO %s (%s, product)
+            attachment_cols = ','.join(table_columns['attachment'])
+            if wiki_name in self.system_wiki_list:
+                for product in Product.select(self.env):
+                    db("""INSERT INTO %s (%s, product)
                                   SELECT %s, '%s' FROM %s
                                   WHERE name='%s' AND version=%s AND 
product='%s'""" %
-                                  (table, cols, cols, product.prefix, table,
-                                   wiki_name, wiki_version, wiki_product))
-                            db("""INSERT INTO attachment (%(cols)s, product)
+                       (table, cols, cols, product.prefix, table,
+                        wiki_name, wiki_version, wiki_product))
+                    db("""INSERT INTO attachment (%(cols)s, product)
                                   SELECT %(cols)s, '%(new_product)s'
                                     FROM attachment a
                                    WHERE type='wiki'
@@ -380,89 +417,40 @@ class MultiProductSystem(Component):
                                           wiki_name=wiki_name,
                                           old_product=wiki_product,
                                           new_product=product.prefix))
-                            self._migrate_attachments(
-                                db("""SELECT type, id, filename
+                    self._migrate_attachments(
+                        db("""SELECT type, id, filename
                                         FROM attachment
                                        WHERE type='wiki'
                                          AND id='%s'
                                          AND product='%s'
                                    """ % (wiki_name, DEFAULT_PRODUCT)),
-                                    to_product=DEFAULT_PRODUCT,
-                                    copy=True
-                            )
-                    else:
-                        self.log.info("Moving wiki page '%s' to default 
product", wiki_name)
-                        db("""UPDATE wiki
+                        to_product=DEFAULT_PRODUCT,
+                        copy=True
+                    )
+            else:
+                self.log.info("Moving wiki page '%s' to default product",
+                              wiki_name)
+                db("""UPDATE wiki
                               SET product='%s'
                               WHERE name='%s' AND version=%s AND product='%s'
                            """ % (DEFAULT_PRODUCT,
                                   wiki_name, wiki_version, wiki_product))
-                        db("""UPDATE attachment
+                db("""UPDATE attachment
                                  SET product='%s'
                                WHERE type='wiki'
                                  AND id='%s'
                                  AND product='%s'
                            """ % (DEFAULT_PRODUCT, wiki_name, wiki_product))
-                        self._migrate_attachments(
-                            db("""SELECT type, id, filename
+                self._migrate_attachments(
+                    db("""SELECT type, id, filename
                                     FROM attachment
                                    WHERE type='wiki'
                                      AND id='%s'
                                      AND product='%s'
                                """ % (wiki_name, DEFAULT_PRODUCT)),
-                            to_product=DEFAULT_PRODUCT,
-                        )
-
-                drop_temp_table(temp_table_name)
-
-                # soft link existing repositories to default product
-                repositories_linked = []
-                for id, name in db("""SELECT id, value FROM repository
-                                      WHERE name='name'"""):
-                    if id in repositories_linked:
-                        continue
-                    db("""INSERT INTO repository (id, name, value)
-                          VALUES (%s, 'product', '%s')""" %
-                       (id, DEFAULT_PRODUCT))
-                    repositories_linked.append(id)
-                    self.log.info("Repository '%s' (%s) soft linked to default 
product", name, id)
-
-                # Update system tables
-                # Upgrade schema
-                self.log.info("Migrating system tables to a new schema")
-                for table in SYSTEM_TABLES:
-                    temp_table_name, cols = create_temp_table(table)
-                    db("""INSERT INTO %s (%s, product)
-                          SELECT %s,'' FROM %s""" %
-                       (table, cols, cols, temp_table_name))
-                    drop_temp_table(temp_table_name)
-
-                # enable multi product hooks in environment configuration
-                import multiproduct.hooks
-                import inspect
-                config_update = False
-                hook_path = 
os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
-                if not 'environment_factory' in self.env.config['trac']:
-                    self.env.config['trac'].set('environment_factory', 
hook_path)
-                    config_update = True
-                if not 'request_factory' in self.env.config['trac']:
-                    self.env.config['trac'].set('request_factory', hook_path)
-                    config_update = True
-                if config_update:
-                    self.log.info("Enabling multi product hooks in environment 
configuration")
-                    self.env.config.save()
-
-                db_installed_version = self._update_db_version(db, 3)
-
-            if db_installed_version < 4:
-                self.log.debug("creating additional db tables for %s plugin." %
-                               PLUGIN_NAME)
-                db_connector, dummy = 
DatabaseManager(self.env)._get_connector()
-                for statement in 
db_connector.to_sql(ProductSetting._get_schema()):
-                    db(statement)
-                db_installed_version = self._update_db_version(db, 4)
-
-            self.env.enable_multiproduct_schema(True)
+                    to_product=DEFAULT_PRODUCT,
+                )
+        self._drop_temp_table(db, temp_table_name)
 
     def _migrate_attachments(self, attachments, to_product=None, copy=False):
         for type, id, filename in attachments:
@@ -498,6 +486,56 @@ class MultiProductSystem(Component):
                     filename, type, id, str(err)
                 )
 
+    def _soft_link_repositories_to_default_product(self, db):
+        # soft link existing repositories to default product
+        repositories_linked = []
+        for id, name in db("""SELECT id, value FROM repository
+                                      WHERE name='name'"""):
+            if id in repositories_linked:
+                continue
+            db("""INSERT INTO repository (id, name, value)
+                          VALUES (%s, 'product', '%s')""" %
+               (id, DEFAULT_PRODUCT))
+            repositories_linked.append(id)
+            self.log.info("Repository '%s' (%s) soft linked to default 
product",
+                          name, id)
+
+    def _upgrade_table_system(self, SYSTEM_TABLES, create_temp_table, db):
+        # Update system tables
+        # Upgrade schema
+        self.log.info("Migrating system tables to a new schema")
+        for table in SYSTEM_TABLES:
+            temp_table_name, cols = create_temp_table(table)
+            db("""INSERT INTO %s (%s, product)
+                          SELECT %s,'' FROM %s""" %
+               (table, cols, cols, temp_table_name))
+            self._drop_temp_table(db, temp_table_name)
+
+    def _enable_multiproduct_hooks(self):
+        # enable multi product hooks in environment configuration
+        import multiproduct.hooks
+        import inspect
+
+        config_update = False
+        hook_path = os.path.realpath(inspect.getsourcefile(multiproduct.hooks))
+        if not 'environment_factory' in self.env.config['trac']:
+            self.env.config['trac'].set('environment_factory', hook_path)
+            config_update = True
+        if not 'request_factory' in self.env.config['trac']:
+            self.env.config['trac'].set('request_factory', hook_path)
+            config_update = True
+        if config_update:
+            self.log.info(
+                "Enabling multi product hooks in environment configuration")
+            self.env.config.save()
+
+    def _create_product_tables_for_plugins(self, db):
+        self.log.debug("creating additional db tables for %s plugin." %
+                       PLUGIN_NAME)
+        db_connector, dummy = DatabaseManager(self.env)._get_connector()
+        for statement in db_connector.to_sql(ProductSetting._get_schema()):
+            db(statement)
+
     # IResourceChangeListener methods
     def match_resource(self, resource):
         return isinstance(resource, Product)

Modified: bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py
URL: 
http://svn.apache.org/viewvc/bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py?rev=1476118&r1=1476117&r2=1476118&view=diff
==============================================================================
--- bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py (original)
+++ bloodhound/trunk/bloodhound_multiproduct/tests/upgrade.py Fri Apr 26 
09:10:14 2013
@@ -33,6 +33,7 @@ from trac.test import Environment
 from trac.ticket import Ticket
 from trac.wiki import WikiPage
 
+from multiproduct.api import MultiProductSystem
 from multiproduct.env import ProductEnvironment
 from multiproduct.model import Product
 
@@ -43,13 +44,9 @@ BLOODHOUND_TABLES = (
 )
 
 TABLES_WITH_PRODUCT_FIELD = (
-    'component',
-    'milestone',
-    'version',
-    'enum',
-    'permission',
-    'wiki',
-    'report',
+    'ticket', 'ticket_change', 'ticket_custom', 'attachment', 'component',
+    'milestone', 'wiki', 'report',
+    'version', 'enum', 'permission', 'system',
 )
 
 
@@ -57,12 +54,12 @@ class EnvironmentUpgradeTestCase(unittes
     def setUp(self):
         self.env_path = tempfile.mkdtemp('multiproduct-tempenv')
         self.env = Environment(self.env_path, create=True)
-        self.enabled_components = []
         DummyPlugin.version = 1
 
-    def test_upgrade_environment(self):
+    def test_can_upgrade_environment_with_multi_product_disabled(self):
         self.env.upgrade()
 
+        # Multiproduct was not enabled so multiproduct tables should not exist
         with self.env.db_direct_transaction as db:
             for table in BLOODHOUND_TABLES:
                 with self.assertFailsWithMissingTable():
@@ -72,7 +69,7 @@ class EnvironmentUpgradeTestCase(unittes
                 with self.assertFailsWithMissingColumn():
                     db("SELECT product FROM %s" % table)
 
-    def test_upgrade_environment_to_multiproduct(self):
+    def 
test_upgrade_creates_multi_product_tables_and_adds_product_column(self):
         self._enable_multiproduct()
         self.env.upgrade()
 
@@ -83,89 +80,38 @@ class EnvironmentUpgradeTestCase(unittes
             for table in TABLES_WITH_PRODUCT_FIELD:
                 db("SELECT product FROM %s" % table)
 
-    def test_upgrade_plugin(self):
-        self._enable_component(DummyPlugin)
-        self.env.upgrade()
-
-        with self.env.db_direct_transaction as db:
-            db("SELECT v1 FROM dummy_table")
-            with self.assertFailsWithMissingColumn():
-                db("SELECT v2 FROM dummy_table")
-
-        DummyPlugin.version = 2
-        self.env.upgrade()
-
-        with self.env.db_direct_transaction as db:
-            db("SELECT v2 FROM dummy_table")
-
-    def test_upgrade_plugin_to_multiproduct(self):
+    def test_upgrade_creates_default_product(self):
         self._enable_multiproduct()
-        self._enable_component(DummyPlugin)
         self.env.upgrade()
 
-        with self.env.db_direct_transaction as db:
-            db("SELECT * FROM dummy_table")
-            db("""SELECT * FROM "@_dummy_table" """)
+        products = Product.select(self.env)
+        self.assertEqual(len(products), 1)
 
-    def test_upgrade_existing_plugin_to_multiproduct(self):
-        self._enable_component(DummyPlugin)
-        self.env.upgrade()
-        with self.env.db_direct_transaction as db:
-            with self.assertFailsWithMissingTable():
-                db("""SELECT * FROM "@_dummy_table" """)
-
-        self._enable_multiproduct()
-        self.env.upgrade()
-        with self.env.db_direct_transaction as db:
-            db("SELECT * FROM dummy_table")
-            db("""SELECT * FROM "@_dummy_table" """)
-
-    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
-        self._enable_component(DummyPlugin)
-        self.env.upgrade()
-        with self.env.db_direct_transaction as db:
-            for i in range(5):
-                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
-            self.assertEqual(
-                len(db("SELECT * FROM dummy_table")), 5)
-
-        self._enable_multiproduct()
-        self.env.upgrade()
-        with self.env.db_direct_transaction as db:
-            self.assertEqual(
-                len(db('SELECT * FROM "dummy_table"')), 5)
-            self.assertEqual(
-                len(db('SELECT * FROM "@_dummy_table"')), 0)
-
-    def test_creating_new_product_calls_environment_created(self):
-        self._enable_component(DummyPlugin)
-        self._enable_multiproduct()
-        self.env.upgrade()
-
-        prod = Product(self.env)
-        prod.update_field_dict(dict(prefix='p1'))
-        ProductEnvironment(self.env, prod, create=True)
-        with self.env.db_direct_transaction as db:
-            db('SELECT * FROM "p1_dummy_table"')
-
-    def test_upgrade_moves_tickets_to_default_product(self):
+    def test_upgrade_moves_tickets_and_related_objects_to_default_prod(self):
+        self._add_custom_field('custom_field')
         with self.env.db_direct_transaction as db:
             db("""INSERT INTO ticket (id) VALUES (1)""")
             db("""INSERT INTO attachment (type, id)
-                         VALUES ('ticket', '1')""")
+                       VALUES ('ticket', '1')""")
+            db("""INSERT INTO ticket_custom (ticket, name, value)
+                       VALUES (1, 'custom_field', '42')""")
+            db("""INSERT INTO ticket_change (ticket, time, field)
+                       VALUES (1, 42, 'summary')""")
 
         self._enable_multiproduct()
         self.env.upgrade()
 
-        with self.env.db_direct_transaction as db:
-            self.assertEqual(
-                len(db("""SELECT * FROM ticket WHERE product='@'""")), 1)
-            self.assertEqual(
-                len(db("""SELECT * FROM attachment
-                          WHERE product='@'
-                            AND type='ticket'""")), 1)
+        with self.product('@'):
+            ticket = Ticket(self.env, 1)
+            attachments = list(Attachment.select(self.env,
+                                                 ticket.resource.realm,
+                                                 ticket.resource.id))
+            self.assertEqual(len(attachments), 1)
+            self.assertEqual(ticket['custom_field'], '42')
+            changes = ticket.get_changelog()
+            self.assertEqual(len(changes), 3)
 
-    def test_upgrade_moves_wikis_to_default_product(self):
+    def test_upgrade_moves_custom_wikis_to_default_product(self):
         with self.env.db_direct_transaction as db:
             db("""INSERT INTO wiki (name, version) VALUES ('MyPage', 1)""")
             db("""INSERT INTO attachment (type, id)
@@ -205,23 +151,44 @@ class EnvironmentUpgradeTestCase(unittes
                            WHERE product=''
                              AND type='wiki'""")), 1)
 
-    def test_can_upgrade_database_with_orphaned_attachments(self):
+    def test_upgrade_copies_content_of_system_tables_to_all_products(self):
+        mp = MultiProductSystem(self.env)
         with self.env.db_direct_transaction as db:
-            db("""INSERT INTO attachment (id, type)
-                       VALUES ('5', 'ticket')""")
-            db("""INSERT INTO attachment (id, type)
-                       VALUES ('MyWiki', 'wiki')""")
+            mp._add_column_product_to_ticket(db)
+            mp._create_multiproduct_tables(db)
+            mp._update_db_version(db, 1)
+            for i in range(1, 6):
+                db("""INSERT INTO bloodhound_product (prefix, name)
+                           VALUES ('p%d', 'Product 1')""" % i)
+            for table in ('component', 'milestone', 'enum', 'version',
+                          'permission', 'report'):
+                db("""DELETE FROM %s""" % table)
+            db("""INSERT INTO component (name) VALUES ('foobar')""")
+            db("""INSERT INTO milestone (name) VALUES ('foobar')""")
+            db("""INSERT INTO version (name) VALUES ('foobar')""")
+            db("""INSERT INTO enum (type, name) VALUES ('a', 'b')""")
+            db("""INSERT INTO permission VALUES ('x', 'TICKET_VIEW')""")
+            db("""INSERT INTO wiki (name, version) VALUES ('WikiStart', 1)""")
+            db("""INSERT INTO report (title) VALUES ('x')""")
 
         self._enable_multiproduct()
         self.env.upgrade()
 
-    def test_can_upgrade_database_with_text_attachment_ids(self):
         with self.env.db_direct_transaction as db:
-            db("""INSERT INTO attachment (id, type)
-                       VALUES ('abc', 'ticket')""")
-
-        self._enable_multiproduct()
-        self.env.upgrade()
+            for table in ('component', 'milestone', 'version', 'enum',
+                          'report'):
+                rows = db("SELECT * FROM %s" % table)
+                self.assertEqual(
+                    len(rows), 6,
+                    "Wrong number of lines in %s (%d instead of %d)\n%s"
+                    % (table, len(rows), 6, rows))
+            for table in ('wiki', 'permission'):
+                # Permissions and wikis also hold rows for global product.
+                rows = db("SELECT * FROM %s" % table)
+                self.assertEqual(
+                    len(rows), 7,
+                    "Wrong number of lines in %s (%d instead of %d)\n%s"
+                    % (table, len(rows), 7, rows))
 
     def test_upgrading_database_moves_attachment_to_correct_product(self):
         ticket = self.insert_ticket('ticket')
@@ -258,26 +225,148 @@ class EnvironmentUpgradeTestCase(unittes
         for attachment in attachments:
             self.assertEqual(attachment.open().read(), 'Hello World!')
 
+    def test_can_upgrade_database_with_ticket_attachment_with_text_ids(self):
+        with self.env.db_direct_transaction as db:
+            db("""INSERT INTO attachment (id, type)
+                       VALUES ('abc', 'ticket')""")
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+    def test_can_upgrade_database_with_orphaned_attachments(self):
+        with self.env.db_direct_transaction as db:
+            db("""INSERT INTO attachment (id, type)
+                       VALUES ('5', 'ticket')""")
+            db("""INSERT INTO attachment (id, type)
+                       VALUES ('MyWiki', 'wiki')""")
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+    def test_can_upgrade_multi_product_from_v1(self):
+        mp = MultiProductSystem(self.env)
+        with self.env.db_direct_transaction as db:
+            mp._add_column_product_to_ticket(db)
+            mp._create_multiproduct_tables(db)
+            mp._update_db_version(db, 1)
+
+            db("""INSERT INTO bloodhound_product (prefix, name)
+                       VALUES ('p1', 'Product 1')""")
+            db("""INSERT INTO ticket (id, product)
+                       VALUES (1, 'Product 1')""")
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+        with self.product('p1'):
+            Ticket(self.env, 1)
+
+    def test_can_upgrade_multi_product_from_v2(self):
+        mp = MultiProductSystem(self.env)
+        with self.env.db_direct_transaction as db:
+            mp._add_column_product_to_ticket(db)
+            mp._create_multiproduct_tables(db)
+            mp._replace_product_on_ticket_with_product_prefix(db)
+            mp._update_db_version(db, 2)
+
+            db("""INSERT INTO bloodhound_product (prefix, name)
+                       VALUES ('p1', 'Product 1')""")
+            db("""INSERT INTO ticket (id, product)
+                       VALUES (1, 'p1')""")
+            db("""INSERT INTO ticket (id)
+                       VALUES (2)""")
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+        with self.product('p1'):
+            Ticket(self.env, 1)
+        with self.product('@'):
+            Ticket(self.env, 2)
+
+    def test_upgrade_plugin(self):
+        self._enable_component(DummyPlugin)
+        self.env.upgrade()
+
+        with self.env.db_direct_transaction as db:
+            db("SELECT v1 FROM dummy_table")
+            with self.assertFailsWithMissingColumn():
+                db("SELECT v2 FROM dummy_table")
+
+        DummyPlugin.version = 2
+        self.env.upgrade()
+
+        with self.env.db_direct_transaction as db:
+            db("SELECT v2 FROM dummy_table")
+
+    def test_upgrade_plugin_to_multiproduct(self):
+        self._enable_multiproduct()
+        self._enable_component(DummyPlugin)
+        self.env.upgrade()
+
+        with self.env.db_direct_transaction as db:
+            db("SELECT * FROM dummy_table")
+            db("""SELECT * FROM "@_dummy_table" """)
+
+    def test_upgrade_existing_plugin_to_multiproduct(self):
+        self._enable_component(DummyPlugin)
+        self.env.upgrade()
+        with self.env.db_direct_transaction as db:
+            with self.assertFailsWithMissingTable():
+                db("""SELECT * FROM "@_dummy_table" """)
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+        with self.env.db_direct_transaction as db:
+            db("SELECT * FROM dummy_table")
+            db("""SELECT * FROM "@_dummy_table" """)
+
+    def test_upgrading_existing_plugin_leaves_data_in_global_env(self):
+        self._enable_component(DummyPlugin)
+        self.env.upgrade()
+        with self.env.db_direct_transaction as db:
+            for i in range(5):
+                db("INSERT INTO dummy_table (v1) VALUES ('%d')" % i)
+            self.assertEqual(
+                len(db("SELECT * FROM dummy_table")), 5)
+
+        self._enable_multiproduct()
+        self.env.upgrade()
+        with self.env.db_direct_transaction as db:
+            self.assertEqual(
+                len(db('SELECT * FROM "dummy_table"')), 5)
+            self.assertEqual(
+                len(db('SELECT * FROM "@_dummy_table"')), 0)
+
+    def test_creating_new_product_calls_environment_created(self):
+        self._enable_component(DummyPlugin)
+        self._enable_multiproduct()
+        self.env.upgrade()
+
+        prod = Product(self.env)
+        prod.update_field_dict(dict(prefix='p1'))
+        ProductEnvironment(self.env, prod, create=True)
+        with self.env.db_direct_transaction as db:
+            db('SELECT * FROM "p1_dummy_table"')
+
     def _enable_multiproduct(self):
-        self.env.config.set('components', 'multiproduct.*', 'enabled')
-        self.env.config.save()
-        self._reload_environment()
-        self._reenable_components()
+        self._update_config('components', 'multiproduct.*', 'enabled')
+
+    def _add_custom_field(self, field_name):
+        self._update_config('ticket-custom', field_name, 'text')
 
     def _enable_component(self, cls):
-        self.env.config.set('components',
-                            '%s.%s' % (cls.__module__, cls.__name__),
-                            'enabled')
-        self.enabled_components.append(cls)
-        self.env.compmgr.enabled[cls] = True
+        self._update_config(
+            'components',
+            '%s.%s' % (cls.__module__, cls.__name__),
+            'enabled'
+        )
 
-    def _reload_environment(self):
+    def _update_config(self, section, key, value):
+        self.env.config.set(section, key, value)
+        self.env.config.save()
         self.env = Environment(self.env_path)
 
-    def _reenable_components(self):
-        for cls in self.enabled_components:
-            self.env.compmgr.enabled[cls] = True
-
     def _create_file_with_content(self, content):
         filename = str(uuid.uuid4())[:6]
         path = os.path.join(self.env_path, filename)


Reply via email to