github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/cmd/dosa/schema_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package main
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"os"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/uber-go/dosa"
    33  	"github.com/uber-go/dosa/mocks"
    34  
    35  	_ "github.com/uber-go/dosa/connectors/devnull"
    36  )
    37  
    38  func getTestEntityNameMap() map[string]bool {
    39  	return map[string]bool{
    40  		"awesome_test_entity":           true,
    41  		"testnullableentity":            true,
    42  		"named_import_entity":           true,
    43  		"testnullablenamedimportentity": true,
    44  	}
    45  }
    46  
    47  func TestScopeFlag_String(t *testing.T) {
    48  	f := scopeFlag("")
    49  	f.setString("foo.bar.baz")
    50  	assert.Equal(t, "foo_bar_baz", f.String())
    51  
    52  	err := f.UnmarshalFlag("qux.quux.corge")
    53  	assert.NoError(t, err)
    54  	assert.Equal(t, "qux_quux_corge", f.String())
    55  }
    56  
    57  func TestSchema_ExpandDirectories(t *testing.T) {
    58  	assert := assert.New(t)
    59  	const tmpdir = ".testexpanddirectories"
    60  	os.RemoveAll(tmpdir)
    61  	defer os.RemoveAll(tmpdir)
    62  
    63  	if err := os.Mkdir(tmpdir, 0770); err != nil {
    64  		t.Fatalf("can't create %s: %s", tmpdir, err)
    65  	}
    66  	// note: these must be in lexical order :(
    67  	dirs := []string{"a", "a/b", "c", "c/d", "c/e"}
    68  
    69  	os.Chdir(tmpdir)
    70  	for _, dirToCreate := range dirs {
    71  		os.Mkdir(dirToCreate, 0770)
    72  	}
    73  	os.Create("a/b/file")
    74  
    75  	cases := []struct {
    76  		args []string
    77  		dirs []string
    78  		err  error
    79  	}{
    80  		{
    81  			args: []string{},
    82  			dirs: []string{"."},
    83  		},
    84  		{
    85  			args: []string{"."},
    86  			dirs: []string{"."},
    87  		},
    88  		{
    89  			args: []string{"./..."},
    90  			dirs: append([]string{"."}, dirs...),
    91  		},
    92  		{
    93  			args: []string{"bogus"},
    94  			err:  errors.New("no such file or directory"),
    95  		},
    96  		{
    97  			args: []string{"a/b/file"},
    98  			err:  errors.New("not a directory"),
    99  		},
   100  	}
   101  
   102  	for _, c := range cases {
   103  		dirs, err := expandDirectories(c.args)
   104  		if c.err != nil {
   105  			assert.Contains(err.Error(), c.err.Error())
   106  		} else {
   107  			assert.Nil(err)
   108  			assert.Equal(c.dirs, dirs)
   109  		}
   110  	}
   111  	os.Chdir("..")
   112  }
   113  
   114  func TestSchema_ServiceInference(t *testing.T) {
   115  	tcs := []struct {
   116  		serviceName string
   117  		scope       string
   118  		expected    string
   119  	}{
   120  		//  service = "", scope = "" -> default
   121  		{
   122  			expected: _defServiceName,
   123  		},
   124  		//  service = "", scope != prod -> default
   125  		{
   126  			scope:    "not-production",
   127  			expected: _defServiceName,
   128  		},
   129  		//  service = "", scope = prod -> prod
   130  		{
   131  			scope:    _prodScope,
   132  			expected: _prodServiceName,
   133  		},
   134  		//  service = "foo", scope = "" -> "foo"
   135  		{
   136  			serviceName: "foo",
   137  			expected:    "foo",
   138  		},
   139  		//  service = "bar", scope != prod -> "bar"
   140  		{
   141  			serviceName: "bar",
   142  			scope:       "bar",
   143  			expected:    "bar",
   144  		},
   145  		//  service = "baz", scope = prod -> "baz"
   146  		{
   147  			serviceName: "baz",
   148  			scope:       _prodScope,
   149  			expected:    "baz",
   150  		},
   151  	}
   152  
   153  	for _, tc := range tcs {
   154  		for _, cmd := range []string{"check", "upsert", "status"} {
   155  			os.Args = []string{
   156  				"dosa",
   157  				"--service", tc.serviceName,
   158  				"--connector", "devnull",
   159  				"schema",
   160  				cmd,
   161  				"--prefix", "foo",
   162  				"--scope", tc.scope,
   163  				"../../testentity",
   164  			}
   165  			main()
   166  			assert.Equal(t, options.ServiceName, tc.expected)
   167  		}
   168  	}
   169  }
   170  
   171  func TestSchema_PrefixRequired(t *testing.T) {
   172  	for _, cmd := range []string{"check", "upsert"} {
   173  		c := StartCapture()
   174  		exit = func(r int) {}
   175  		os.Args = []string{
   176  			"dosa",
   177  			"schema",
   178  			cmd,
   179  			"../../testentity",
   180  		}
   181  		main()
   182  		assert.Contains(t, c.stop(true), "--prefix' was not specified")
   183  	}
   184  }
   185  
   186  func TestSchema_InvalidDirectory(t *testing.T) {
   187  	// dump is a special snowflake
   188  	prefixMap := map[string]bool{
   189  		"check":  true,
   190  		"upsert": true,
   191  		"dump":   false,
   192  	}
   193  	for cmd, hasPrefix := range prefixMap {
   194  		c := StartCapture()
   195  		exit = func(r int) {}
   196  		os.Args = []string{
   197  			"dosa",
   198  			"schema",
   199  			cmd,
   200  		}
   201  		if hasPrefix {
   202  			os.Args = append(os.Args, "--prefix", "foo")
   203  		}
   204  		os.Args = append(os.Args, []string{
   205  			"-e", "testentity.go",
   206  			"../../testentity",
   207  			"/dev/null",
   208  		}...)
   209  		main()
   210  		assert.Contains(t, c.stop(true), "\"/dev/null\" is not a directory")
   211  	}
   212  }
   213  
   214  func TestSchema_NoEntitiesFound(t *testing.T) {
   215  	// dump is a special snowflake
   216  	prefixMap := map[string]bool{
   217  		"check":  true,
   218  		"upsert": true,
   219  		"dump":   false,
   220  	}
   221  	for cmd, hasPrefix := range prefixMap {
   222  		c := StartCapture()
   223  		exit = func(r int) {}
   224  		os.Args = []string{
   225  			"dosa",
   226  			"schema",
   227  			cmd,
   228  		}
   229  		if hasPrefix {
   230  			os.Args = append(os.Args, "--prefix", "foo")
   231  		}
   232  		os.Args = append(os.Args, []string{
   233  			"-e", "testentity.go",
   234  			"-e", "keyvalue.go",
   235  			"-e", "named_import_testentity.go",
   236  			"../../testentity",
   237  		}...)
   238  		main()
   239  		assert.Contains(t, c.stop(true), "no entities found")
   240  	}
   241  }
   242  
   243  // There are 4 tests to perform against each operation
   244  // 1 - success case, displays scope
   245  // 2 - failure case, couldn't initialize the connector
   246  // 3 - failure case, the connector API call fails
   247  // 4 - failure case, problems with the directories on the command line or the entities
   248  
   249  func TestSchema_Check_Happy(t *testing.T) {
   250  	ctrl := gomock.NewController(t)
   251  	defer ctrl.Finish()
   252  
   253  	exit = func(r int) {
   254  		assert.Equal(t, 0, r)
   255  	}
   256  	dosa.RegisterConnector("mock", func(dosa.CreationArgs) (dosa.Connector, error) {
   257  		mc := mocks.NewMockConnector(ctrl)
   258  		mc.EXPECT().CheckSchema(gomock.Any(), "scope", "foo", gomock.Any()).
   259  			Do(func(ctx context.Context, scope string, namePrefix string, ed []*dosa.EntityDefinition) {
   260  				dl, ok := ctx.Deadline()
   261  				assert.True(t, ok)
   262  				assert.True(t, dl.After(time.Now()))
   263  				assert.Equal(t, 6, len(ed))
   264  				nameMap := getTestEntityNameMap()
   265  				for _, e := range ed {
   266  					assert.True(t, nameMap[e.Name])
   267  				}
   268  			}).Return(int32(1), nil)
   269  		return mc, nil
   270  	})
   271  	os.Args = []string{"dosa", "--connector", "mock", "schema", "check", "--prefix", "foo", "-e", "_test.go", "-e", "excludeme.go", "-s", "scope", "-v", "../../testentity"}
   272  	main()
   273  }
   274  
   275  func TestSchema_Status_Happy(t *testing.T) {
   276  	ctrl := gomock.NewController(t)
   277  	defer ctrl.Finish()
   278  
   279  	exit = func(r int) {
   280  		assert.Equal(t, 0, r)
   281  	}
   282  	dosa.RegisterConnector("mock", func(dosa.CreationArgs) (dosa.Connector, error) {
   283  		mc := mocks.NewMockConnector(ctrl)
   284  		mc.EXPECT().CheckSchemaStatus(gomock.Any(), "scope", "foo", gomock.Any()).
   285  			Do(func(ctx context.Context, scope string, namePrefix string, version int32) {
   286  				dl, ok := ctx.Deadline()
   287  				assert.True(t, ok)
   288  				assert.True(t, dl.After(time.Now()))
   289  				assert.Equal(t, int32(12), version)
   290  			}).Return(&dosa.SchemaStatus{Version: int32(12)}, nil)
   291  		return mc, nil
   292  	})
   293  	os.Args = []string{"dosa", "--connector", "mock", "schema", "status", "--prefix", "foo", "-s", "scope", "-v", "--version", "12"}
   294  	main()
   295  }
   296  
   297  func TestSchema_Upsert_Happy(t *testing.T) {
   298  	ctrl := gomock.NewController(t)
   299  	defer ctrl.Finish()
   300  
   301  	exit = func(r int) {
   302  		assert.Equal(t, 0, r)
   303  	}
   304  
   305  	dosa.RegisterConnector("mock", func(dosa.CreationArgs) (dosa.Connector, error) {
   306  		mc := mocks.NewMockConnector(ctrl)
   307  		mc.EXPECT().UpsertSchema(gomock.Any(), "scope", "foo", gomock.Any()).
   308  			Do(func(ctx context.Context, scope string, namePrefix string, ed []*dosa.EntityDefinition) {
   309  				dl, ok := ctx.Deadline()
   310  				assert.True(t, ok)
   311  				assert.True(t, dl.After(time.Now()))
   312  				assert.Equal(t, 6, len(ed))
   313  
   314  				nameMap := getTestEntityNameMap()
   315  				for _, e := range ed {
   316  					assert.True(t, nameMap[e.Name])
   317  				}
   318  			}).Return(&dosa.SchemaStatus{Version: int32(1)}, nil)
   319  		return mc, nil
   320  	})
   321  	os.Args = []string{"dosa", "--connector", "mock", "schema", "upsert", "--prefix", "foo", "-e", "_test.go", "-e", "excludeme.go", "-s", "scope", "-v", "../../testentity"}
   322  	main()
   323  }
   324  
   325  func TestSchema_Dump_InvalidFormat(t *testing.T) {
   326  	c := StartCapture()
   327  	exit = func(r int) {}
   328  	os.Args = []string{"dosa", "schema", "dump", "-f", "invalid", "../../testentity"}
   329  	main()
   330  	assert.Contains(t, c.stop(true), "Invalid value")
   331  }
   332  
   333  func TestSchema_Dump_CQL(t *testing.T) {
   334  	c := StartCapture()
   335  	exit = func(r int) {}
   336  	os.Args = []string{"dosa", "schema", "dump", "-v", "../../testentity"}
   337  	main()
   338  	output := c.stop(false)
   339  	assert.Contains(t, output, "executing schema dump")
   340  	assert.Contains(t, output, "create table \"awesome_test_entity\" (\"an_uuid_key\" uuid, \"strkey\" text, \"int64key\" bigint")
   341  }
   342  
   343  func TestSchema_Dump_UQL(t *testing.T) {
   344  	c := StartCapture()
   345  	exit = func(r int) {}
   346  	os.Args = []string{"dosa", "schema", "dump", "-f", "uql", "-v", "../../testentity"}
   347  	main()
   348  	output := c.stop(false)
   349  	assert.Contains(t, output, "executing schema dump")
   350  	assert.Contains(t, output, "CREATE TABLE awesome_test_entity")
   351  	assert.Contains(t, output, "an_int64_value int64;")
   352  	assert.Contains(t, output, "PRIMARY KEY (an_uuid_key, strkey ASC, int64key DESC);")
   353  }
   354  
   355  func TestSchema_Dump_Avro(t *testing.T) {
   356  	c := StartCapture()
   357  	exit = func(r int) {}
   358  	os.Args = []string{"dosa", "schema", "dump", "-f", "avro", "-v", "../../testentity"}
   359  	main()
   360  	output := c.stop(false)
   361  	assert.Contains(t, output, "executing schema dump")
   362  }