1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 Free Software Foundation, Inc
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 from pymongo
import Connection
20 from mediagoblin
.tests
.tools
import (
21 install_fixtures_simple
, assert_db_meets_expected
)
22 from mediagoblin
.db
.util
import RegisterMigration
, MigrationManager
, ObjectId
24 # This one will get filled with local migrations
25 TEST_MIGRATION_REGISTRY
= {}
26 # this one won't get filled
27 TEST_EMPTY_MIGRATION_REGISTRY
= {}
29 MIGRATION_DB_NAME
= u
'__mediagoblin_test_migrations__'
32 ######################
33 # Fake test migrations
34 ######################
36 @RegisterMigration(1, TEST_MIGRATION_REGISTRY
)
37 def creature_add_magical_powers(database
):
39 Add lists of magical powers.
41 This defaults to [], an empty list. Since we haven't declared any
42 magical powers, all existing monsters should
44 database
['creatures'].update(
45 {'magical_powers': {'$exists': False}},
46 {'$set': {'magical_powers': []}},
50 @RegisterMigration(2, TEST_MIGRATION_REGISTRY
)
51 def creature_rename_num_legs_to_num_limbs(database
):
53 It turns out we want to track how many limbs a creature has, not
54 just how many legs. We don't care about the ambiguous distinction
55 between arms/legs currently.
57 # $rename not available till 1.7.2+, Debian Stable only includes
58 # 1.4.4... we should do renames manually for now :(
60 collection
= database
['creatures']
61 target
= collection
.find(
62 {'num_legs': {'$exists': True}})
64 for document
in target
:
65 # A lame manual renaming.
66 document
['num_limbs'] = document
.pop('num_legs')
67 collection
.save(document
)
70 @RegisterMigration(3, TEST_MIGRATION_REGISTRY
)
71 def creature_remove_is_demon(database
):
73 It turns out we don't care much about whether creatures are demons
76 database
['creatures'].update(
77 {'is_demon': {'$exists': True}},
78 {'$unset': {'is_demon': 1}},
82 @RegisterMigration(4, TEST_MIGRATION_REGISTRY
)
83 def level_exits_dict_to_list(database
):
85 For the sake of the indexes we want to write, and because we
86 intend to add more flexible fields, we want to move level exits
89 {'big_door': 'castle_level_id',
90 'trapdoor': 'dungeon_level_id'}
95 'exits_to': 'castle_level_id'},
97 'exits_to': 'dungeon_level_id'}]
99 collection
= database
['levels']
100 target
= collection
.find(
101 {'exits': {'$type': 3}})
105 for exit_name
, exits_to
in level
['exits'].items():
108 'exits_to': exits_to
})
110 level
['exits'] = new_exits
111 collection
.save(level
)
114 CENTIPEDE_OBJECTID
= ObjectId()
115 WOLF_OBJECTID
= ObjectId()
116 WIZARDSNAKE_OBJECTID
= ObjectId()
118 UNMIGRATED_DBDATA
= {
120 {'_id': CENTIPEDE_OBJECTID
,
124 {'_id': WOLF_OBJECTID
,
128 # don't ask me what a wizardsnake is.
129 {'_id': WIZARDSNAKE_OBJECTID
,
130 'name': 'wizardsnake',
135 'name': 'The Necroplex',
136 'description': 'A complex full of pure deathzone.',
138 'deathwell': 'evilstorm',
139 'portal': 'central_park'}},
141 'name': 'Evil Storm',
142 'description': 'A storm full of pure evil.',
143 'exits': {}}, # you can't escape the evilstorm
144 {'_id': 'central_park',
145 'name': 'Central Park, NY, NY',
146 'description': "New York's friendly Central Park.",
148 'portal': 'necroplex'}}]}
151 EXPECTED_POST_MIGRATION_UNMIGRATED_DBDATA
= {
153 {'_id': CENTIPEDE_OBJECTID
,
156 'magical_powers': []},
157 {'_id': WOLF_OBJECTID
,
160 # kept around namely to check that it *isn't* removed!
161 'magical_powers': []},
162 {'_id': WIZARDSNAKE_OBJECTID
,
163 'name': 'wizardsnake',
165 'magical_powers': []}],
168 'name': 'The Necroplex',
169 'description': 'A complex full of pure deathzone.',
171 {'name': 'deathwell',
172 'exits_to': 'evilstorm'},
174 'exits_to': 'central_park'}]},
176 'name': 'Evil Storm',
177 'description': 'A storm full of pure evil.',
178 'exits': []}, # you can't escape the evilstorm
179 {'_id': 'central_park',
180 'name': 'Central Park, NY, NY',
181 'description': "New York's friendly Central Park.",
184 'exits_to': 'necroplex'}]}]}
186 # We want to make sure that if we're at migration 3, migration 3
187 # doesn't get re-run.
189 SEMI_MIGRATED_DBDATA
= {
191 {'_id': CENTIPEDE_OBJECTID
,
194 'magical_powers': []},
195 {'_id': WOLF_OBJECTID
,
198 # kept around namely to check that it *isn't* removed!
201 'ice_breath', 'death_stare']},
202 {'_id': WIZARDSNAKE_OBJECTID
,
203 'name': 'wizardsnake',
206 'death_rattle', 'sneaky_stare',
207 'slithery_smoke', 'treacherous_tremors'],
211 'name': 'The Necroplex',
212 'description': 'A complex full of pure deathzone.',
214 'deathwell': 'evilstorm',
215 'portal': 'central_park'}},
217 'name': 'Evil Storm',
218 'description': 'A storm full of pure evil.',
219 'exits': {}}, # you can't escape the evilstorm
220 {'_id': 'central_park',
221 'name': 'Central Park, NY, NY',
222 'description': "New York's friendly Central Park.",
224 'portal': 'necroplex'}}]}
227 EXPECTED_POST_MIGRATION_SEMI_MIGRATED_DBDATA
= {
229 {'_id': CENTIPEDE_OBJECTID
,
232 'magical_powers': []},
233 {'_id': WOLF_OBJECTID
,
236 # kept around namely to check that it *isn't* removed!
239 'ice_breath', 'death_stare']},
240 {'_id': WIZARDSNAKE_OBJECTID
,
241 'name': 'wizardsnake',
244 'death_rattle', 'sneaky_stare',
245 'slithery_smoke', 'treacherous_tremors'],
249 'name': 'The Necroplex',
250 'description': 'A complex full of pure deathzone.',
252 {'name': 'deathwell',
253 'exits_to': 'evilstorm'},
255 'exits_to': 'central_park'}]},
257 'name': 'Evil Storm',
258 'description': 'A storm full of pure evil.',
259 'exits': []}, # you can't escape the evilstorm
260 {'_id': 'central_park',
261 'name': 'Central Park, NY, NY',
262 'description': "New York's friendly Central Park.",
265 'exits_to': 'necroplex'}]}]}
268 class TestMigrations(object):
270 # Set up the connection, drop an existing possible database
271 self
.connection
= Connection()
272 self
.connection
.drop_database(MIGRATION_DB_NAME
)
273 self
.db
= Connection()[MIGRATION_DB_NAME
]
274 self
.migration_manager
= MigrationManager(
275 self
.db
, TEST_MIGRATION_REGISTRY
)
276 self
.empty_migration_manager
= MigrationManager(
277 self
.db
, TEST_EMPTY_MIGRATION_REGISTRY
)
278 self
.run_migrations
= []
281 self
.connection
.drop_database(MIGRATION_DB_NAME
)
283 def _record_migration(self
, migration_number
, migration_func
):
284 self
.run_migrations
.append((migration_number
, migration_func
))
286 def test_migrations_registered_and_sorted(self
):
288 Make sure that migrations get registered and are sorted right
289 in the migration manager
291 assert TEST_MIGRATION_REGISTRY
== {
292 1: creature_add_magical_powers
,
293 2: creature_rename_num_legs_to_num_limbs
,
294 3: creature_remove_is_demon
,
295 4: level_exits_dict_to_list
}
296 assert self
.migration_manager
.sorted_migrations
== [
297 (1, creature_add_magical_powers
),
298 (2, creature_rename_num_legs_to_num_limbs
),
299 (3, creature_remove_is_demon
),
300 (4, level_exits_dict_to_list
)]
301 assert self
.empty_migration_manager
.sorted_migrations
== []
303 def test_run_full_migrations(self
):
305 Make sure that running the full migration suite from 0 updates
308 self
.migration_manager
.set_current_migration(0)
309 assert self
.migration_manager
.database_current_migration() == 0
310 install_fixtures_simple(self
.db
, UNMIGRATED_DBDATA
)
311 self
.migration_manager
.migrate_new(post_callback
=self
._record
_migration
)
313 assert self
.run_migrations
== [
314 (1, creature_add_magical_powers
),
315 (2, creature_rename_num_legs_to_num_limbs
),
316 (3, creature_remove_is_demon
),
317 (4, level_exits_dict_to_list
)]
319 assert_db_meets_expected(
320 self
.db
, EXPECTED_POST_MIGRATION_UNMIGRATED_DBDATA
)
322 # Make sure the migration is recorded correctly
323 assert self
.migration_manager
.database_current_migration() == 4
325 # run twice! It should do nothing the second time.
326 # ------------------------------------------------
327 self
.run_migrations
= []
328 self
.migration_manager
.migrate_new(post_callback
=self
._record
_migration
)
329 assert self
.run_migrations
== []
330 assert_db_meets_expected(
331 self
.db
, EXPECTED_POST_MIGRATION_UNMIGRATED_DBDATA
)
332 assert self
.migration_manager
.database_current_migration() == 4
335 def test_run_partial_migrations(self
):
337 Make sure that running full migration suite from 3 only runs
342 def test_migrations_recorded_as_latest(self
):
344 Make sure that if we don't have a migration_status
345 pre-recorded it's marked as the latest
349 def test_no_migrations_recorded_as_zero(self
):
351 Make sure that if we don't have a migration_status
352 but there *are* no migrations that it's marked as 0