@@ -311,6 +311,90 @@ def test_tables_with_prefix(self):
311311 result = connection .tables ()
312312 self .assertEqual (result , [unprefixed_table_name1 ])
313313
314+ def test_create_table (self ):
315+ import operator
316+ from gcloud ._testing import _Monkey
317+ from gcloud .bigtable .happybase import connection as MUT
318+
319+ cluster = _Cluster () # Avoid implicit environ check.
320+ connection = self ._makeOne (autoconnect = False , cluster = cluster )
321+ mock_gc_rule = object ()
322+ called_options = []
323+
324+ def mock_parse_family_option (option ):
325+ called_options .append (option )
326+ return mock_gc_rule
327+
328+ name = 'table-name'
329+ col_fam1 = 'cf1'
330+ col_fam_option1 = object ()
331+ col_fam2 = u'cf2'
332+ col_fam_option2 = object ()
333+ col_fam3 = b'cf3'
334+ col_fam_option3 = object ()
335+ families = {
336+ col_fam1 : col_fam_option1 ,
337+ # A trailing colon is also allowed.
338+ col_fam2 + ':' : col_fam_option2 ,
339+ col_fam3 + b':' : col_fam_option3 ,
340+ }
341+
342+ tables_created = []
343+
344+ def make_table (* args , ** kwargs ):
345+ result = _MockLowLevelTable (* args , ** kwargs )
346+ tables_created .append (result )
347+ return result
348+
349+ with _Monkey (MUT , _LowLevelTable = make_table ,
350+ _parse_family_option = mock_parse_family_option ):
351+ connection .create_table (name , families )
352+
353+ # Just one table would have been created.
354+ table_instance , = tables_created
355+ self .assertEqual (table_instance .args , (name , cluster ))
356+ self .assertEqual (table_instance .kwargs , {})
357+ self .assertEqual (table_instance .create_calls , 1 )
358+
359+ # Check if our mock was called twice, but we don't know the order.
360+ self .assertEqual (
361+ set (called_options ),
362+ set ([col_fam_option1 , col_fam_option2 , col_fam_option3 ]))
363+
364+ # We expect three column family instances created, but don't know the
365+ # order due to non-deterministic dict.items().
366+ col_fam_created = table_instance .col_fam_created
367+ self .assertEqual (len (col_fam_created ), 3 )
368+ col_fam_created .sort (key = operator .attrgetter ('column_family_id' ))
369+ self .assertEqual (col_fam_created [0 ].column_family_id , col_fam1 )
370+ self .assertEqual (col_fam_created [0 ].gc_rule , mock_gc_rule )
371+ self .assertEqual (col_fam_created [0 ].create_calls , 1 )
372+ self .assertEqual (col_fam_created [1 ].column_family_id , col_fam2 )
373+ self .assertEqual (col_fam_created [1 ].gc_rule , mock_gc_rule )
374+ self .assertEqual (col_fam_created [1 ].create_calls , 1 )
375+ self .assertEqual (col_fam_created [2 ].column_family_id ,
376+ col_fam3 .decode ('utf-8' ))
377+ self .assertEqual (col_fam_created [2 ].gc_rule , mock_gc_rule )
378+ self .assertEqual (col_fam_created [2 ].create_calls , 1 )
379+
380+ def test_create_table_bad_type (self ):
381+ cluster = _Cluster () # Avoid implicit environ check.
382+ connection = self ._makeOne (autoconnect = False , cluster = cluster )
383+
384+ name = 'table-name'
385+ families = None
386+ with self .assertRaises (TypeError ):
387+ connection .create_table (name , families )
388+
389+ def test_create_table_bad_value (self ):
390+ cluster = _Cluster () # Avoid implicit environ check.
391+ connection = self ._makeOne (autoconnect = False , cluster = cluster )
392+
393+ name = 'table-name'
394+ families = {}
395+ with self .assertRaises (ValueError ):
396+ connection .create_table (name , families )
397+
314398 def test_delete_table (self ):
315399 from gcloud ._testing import _Monkey
316400 from gcloud .bigtable .happybase import connection as MUT
@@ -376,6 +460,90 @@ def test_compact_table(self):
376460 connection .compact_table (name , major = major )
377461
378462
463+ class Test__parse_family_option (unittest2 .TestCase ):
464+
465+ def _callFUT (self , option ):
466+ from gcloud .bigtable .happybase .connection import _parse_family_option
467+ return _parse_family_option (option )
468+
469+ def test_dictionary_no_keys (self ):
470+ option = {}
471+ result = self ._callFUT (option )
472+ self .assertEqual (result , None )
473+
474+ def test_null (self ):
475+ option = None
476+ result = self ._callFUT (option )
477+ self .assertEqual (result , None )
478+
479+ def test_dictionary_bad_key (self ):
480+ from gcloud ._testing import _Monkey
481+ from gcloud .bigtable .happybase import connection as MUT
482+
483+ warned = []
484+
485+ def mock_warn (msg ):
486+ warned .append (msg )
487+
488+ option = {'badkey' : None }
489+ with _Monkey (MUT , _WARN = mock_warn ):
490+ result = self ._callFUT (option )
491+
492+ self .assertEqual (result , None )
493+ self .assertEqual (len (warned ), 1 )
494+ self .assertIn ('badkey' , warned [0 ])
495+
496+ def test_dictionary_versions_key (self ):
497+ from gcloud .bigtable .column_family import MaxVersionsGCRule
498+
499+ versions = 42
500+ option = {'max_versions' : versions }
501+ result = self ._callFUT (option )
502+
503+ gc_rule = MaxVersionsGCRule (versions )
504+ self .assertEqual (result , gc_rule )
505+
506+ def test_dictionary_ttl_key (self ):
507+ import datetime
508+ from gcloud .bigtable .column_family import MaxAgeGCRule
509+
510+ time_to_live = 24 * 60 * 60
511+ max_age = datetime .timedelta (days = 1 )
512+ option = {'time_to_live' : time_to_live }
513+ result = self ._callFUT (option )
514+
515+ gc_rule = MaxAgeGCRule (max_age )
516+ self .assertEqual (result , gc_rule )
517+
518+ def test_dictionary_both_keys (self ):
519+ import datetime
520+ from gcloud .bigtable .column_family import GCRuleIntersection
521+ from gcloud .bigtable .column_family import MaxAgeGCRule
522+ from gcloud .bigtable .column_family import MaxVersionsGCRule
523+
524+ versions = 42
525+ time_to_live = 24 * 60 * 60
526+ option = {
527+ 'max_versions' : versions ,
528+ 'time_to_live' : time_to_live ,
529+ }
530+ result = self ._callFUT (option )
531+
532+ max_age = datetime .timedelta (days = 1 )
533+ # NOTE: This relies on the order of the rules in the method we are
534+ # calling matching this order here.
535+ gc_rule1 = MaxAgeGCRule (max_age )
536+ gc_rule2 = MaxVersionsGCRule (versions )
537+ gc_rule = GCRuleIntersection (rules = [gc_rule1 , gc_rule2 ])
538+ self .assertEqual (result , gc_rule )
539+
540+ def test_non_dictionary (self ):
541+ option = object ()
542+ self .assertFalse (isinstance (option , dict ))
543+ result = self ._callFUT (option )
544+ self .assertEqual (result , option )
545+
546+
379547class _Client (object ):
380548
381549 def __init__ (self , * args , ** kwargs ):
@@ -418,12 +586,33 @@ def list_tables(self):
418586 return self .list_tables_result
419587
420588
589+ class _MockLowLevelColumnFamily (object ):
590+
591+ def __init__ (self , column_family_id , gc_rule = None ):
592+ self .column_family_id = column_family_id
593+ self .gc_rule = gc_rule
594+ self .create_calls = 0
595+
596+ def create (self ):
597+ self .create_calls += 1
598+
599+
421600class _MockLowLevelTable (object ):
422601
423602 def __init__ (self , * args , ** kwargs ):
424603 self .args = args
425604 self .kwargs = kwargs
426605 self .delete_calls = 0
606+ self .create_calls = 0
607+ self .col_fam_created = []
427608
428609 def delete (self ):
429610 self .delete_calls += 1
611+
612+ def create (self ):
613+ self .create_calls += 1
614+
615+ def column_family (self , column_family_id , gc_rule = None ):
616+ result = _MockLowLevelColumnFamily (column_family_id , gc_rule = gc_rule )
617+ self .col_fam_created .append (result )
618+ return result
0 commit comments