github.com/tuhaihe/gpbackup@v1.0.3/options/options_test.go (about)

     1  package options_test
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  
     7  	"github.com/DATA-DOG/go-sqlmock"
     8  	"github.com/tuhaihe/gp-common-go-libs/dbconn"
     9  	"github.com/tuhaihe/gp-common-go-libs/testhelper"
    10  	"github.com/tuhaihe/gpbackup/options"
    11  	"github.com/spf13/pflag"
    12  
    13  	. "github.com/onsi/ginkgo/v2"
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  var _ = Describe("options", func() {
    18  	var (
    19  		myflags *pflag.FlagSet
    20  	)
    21  	BeforeEach(func() {
    22  		myflags = &pflag.FlagSet{}
    23  		options.SetBackupFlagDefaults(myflags)
    24  	})
    25  	Describe("Options initialization", func() {
    26  		It("returns no included tables when none specified", func() {
    27  			subject, err := options.NewOptions(myflags)
    28  			Expect(err).To(Not(HaveOccurred()))
    29  
    30  			includedTables := subject.GetIncludedTables()
    31  			Expect(includedTables).To(BeEmpty())
    32  			originalIncludedTables := subject.GetOriginalIncludedTables()
    33  			Expect(originalIncludedTables).To(BeEmpty())
    34  		})
    35  		It("returns the include tables when one table in flag", func() {
    36  			err := myflags.Set(options.INCLUDE_RELATION, "foo.bar")
    37  			Expect(err).ToNot(HaveOccurred())
    38  
    39  			subject, err := options.NewOptions(myflags)
    40  			Expect(err).To(Not(HaveOccurred()))
    41  
    42  			includedTables := subject.GetIncludedTables()
    43  			Expect(includedTables).To(HaveLen(1))
    44  			Expect(includedTables[0]).To(Equal("foo.bar"))
    45  			originalIncludedTables := subject.GetOriginalIncludedTables()
    46  			Expect(originalIncludedTables[0]).To(Equal("foo.bar"))
    47  		})
    48  		It("returns an include with special characters besides quote and dot", func() {
    49  			err := myflags.Set(options.INCLUDE_RELATION, `foo '~#$%^&*()_-+[]{}><\|;:/?!\t\n,.bar`)
    50  			Expect(err).ToNot(HaveOccurred())
    51  			subject, err := options.NewOptions(myflags)
    52  			Expect(err).To(Not(HaveOccurred()))
    53  
    54  			includedTables := subject.GetIncludedTables()
    55  			Expect(includedTables).To(HaveLen(1))
    56  			Expect(includedTables[0]).To(Equal(`foo '~#$%^&*()_-+[]{}><\|;:/?!\t\n,.bar`))
    57  		})
    58  		It("returns all included tables when multiple individual flags provided", func() {
    59  			err := myflags.Set(options.INCLUDE_RELATION, "foo.bar")
    60  			Expect(err).ToNot(HaveOccurred())
    61  			err = myflags.Set(options.INCLUDE_RELATION, "bar.baz")
    62  			Expect(err).ToNot(HaveOccurred())
    63  
    64  			subject, err := options.NewOptions(myflags)
    65  			Expect(err).To(Not(HaveOccurred()))
    66  
    67  			includedTables := subject.GetIncludedTables()
    68  			Expect(includedTables).To(HaveLen(2))
    69  			Expect(includedTables[0]).To(Equal("foo.bar"))
    70  			Expect(includedTables[1]).To(Equal("bar.baz"))
    71  		})
    72  		It("returns the text-file tables when specified", func() {
    73  			file, err := ioutil.TempFile("/tmp", "gpbackup_test_options*.txt")
    74  			Expect(err).To(Not(HaveOccurred()))
    75  			defer func() {
    76  				_ = os.Remove(file.Name())
    77  			}()
    78  			_, err = file.WriteString("myschema.mytable\n")
    79  			Expect(err).To(Not(HaveOccurred()))
    80  			_, err = file.WriteString("myschema.mytable2\n")
    81  			Expect(err).To(Not(HaveOccurred()))
    82  			err = file.Close()
    83  			Expect(err).To(Not(HaveOccurred()))
    84  
    85  			err = myflags.Set(options.INCLUDE_RELATION_FILE, file.Name())
    86  			Expect(err).ToNot(HaveOccurred())
    87  			subject, err := options.NewOptions(myflags)
    88  			Expect(err).To(Not(HaveOccurred()))
    89  
    90  			includedTables := subject.GetIncludedTables()
    91  			Expect(includedTables).To(HaveLen(2))
    92  			Expect(includedTables[0]).To(Equal("myschema.mytable"))
    93  			Expect(includedTables[1]).To(Equal("myschema.mytable2"))
    94  		})
    95  		It("sets the INCLUDE_RELATIONS flag from file", func() {
    96  			file, err := ioutil.TempFile("/tmp", "gpbackup_test_options*.txt")
    97  			Expect(err).To(Not(HaveOccurred()))
    98  			defer func() {
    99  				_ = os.Remove(file.Name())
   100  			}()
   101  			_, err = file.WriteString("myschema.mytable\n")
   102  			Expect(err).To(Not(HaveOccurred()))
   103  			_, err = file.WriteString("myschema.mytable2\n")
   104  			Expect(err).To(Not(HaveOccurred()))
   105  			err = file.Close()
   106  			Expect(err).To(Not(HaveOccurred()))
   107  
   108  			err = myflags.Set(options.INCLUDE_RELATION_FILE, file.Name())
   109  			Expect(err).ToNot(HaveOccurred())
   110  			_, err = options.NewOptions(myflags)
   111  			Expect(err).To(Not(HaveOccurred()))
   112  
   113  			includedTables, err := myflags.GetStringArray(options.INCLUDE_RELATION)
   114  			Expect(err).ToNot(HaveOccurred())
   115  			Expect(includedTables).To(HaveLen(2))
   116  			Expect(includedTables[0]).To(Equal("myschema.mytable"))
   117  			Expect(includedTables[1]).To(Equal("myschema.mytable2"))
   118  		})
   119  		It("skips empty lines in files provided for filtering tables", func() {
   120  			file, err := ioutil.TempFile("/tmp", "gpbackup_test_options*.txt")
   121  			Expect(err).To(Not(HaveOccurred()))
   122  			defer func() {
   123  				_ = os.Remove(file.Name())
   124  			}()
   125  			_, err = file.WriteString("myschema.mytable\n")
   126  			Expect(err).To(Not(HaveOccurred()))
   127  			_, err = file.WriteString("\n")
   128  			Expect(err).To(Not(HaveOccurred()))
   129  			Expect(err).To(Not(HaveOccurred()))
   130  			_, err = file.WriteString("\n")
   131  			Expect(err).To(Not(HaveOccurred()))
   132  			_, err = file.WriteString("myschema.mytable2\n")
   133  			Expect(err).To(Not(HaveOccurred()))
   134  			Expect(err).To(Not(HaveOccurred()))
   135  			_, err = file.WriteString("\n")
   136  			Expect(err).To(Not(HaveOccurred()))
   137  			_, err = file.WriteString("\n")
   138  			err = file.Close()
   139  			Expect(err).To(Not(HaveOccurred()))
   140  
   141  			err = myflags.Set(options.EXCLUDE_RELATION_FILE, file.Name())
   142  			Expect(err).ToNot(HaveOccurred())
   143  			_, err = options.NewOptions(myflags)
   144  			Expect(err).To(Not(HaveOccurred()))
   145  
   146  			excludedTables, err := myflags.GetStringArray(options.EXCLUDE_RELATION)
   147  			Expect(err).ToNot(HaveOccurred())
   148  			Expect(excludedTables).To(HaveLen(2))
   149  			Expect(excludedTables[0]).To(Equal("myschema.mytable"))
   150  			Expect(excludedTables[1]).To(Equal("myschema.mytable2"))
   151  		})
   152  		It("skips empty lines in files provided for filtering schemas", func() {
   153  			file, err := ioutil.TempFile("/tmp", "gpbackup_test_options*.txt")
   154  			Expect(err).To(Not(HaveOccurred()))
   155  			defer func() {
   156  				_ = os.Remove(file.Name())
   157  			}()
   158  			_, err = file.WriteString("myschema1\n")
   159  			Expect(err).To(Not(HaveOccurred()))
   160  			_, err = file.WriteString("\n")
   161  			Expect(err).To(Not(HaveOccurred()))
   162  			Expect(err).To(Not(HaveOccurred()))
   163  			_, err = file.WriteString("\n")
   164  			Expect(err).To(Not(HaveOccurred()))
   165  			_, err = file.WriteString("myschema2\n")
   166  			Expect(err).To(Not(HaveOccurred()))
   167  			Expect(err).To(Not(HaveOccurred()))
   168  			_, err = file.WriteString("\n")
   169  			Expect(err).To(Not(HaveOccurred()))
   170  			_, err = file.WriteString("\n")
   171  			err = file.Close()
   172  			Expect(err).To(Not(HaveOccurred()))
   173  
   174  			err = myflags.Set(options.INCLUDE_SCHEMA_FILE, file.Name())
   175  			Expect(err).ToNot(HaveOccurred())
   176  			_, err = options.NewOptions(myflags)
   177  			Expect(err).To(Not(HaveOccurred()))
   178  
   179  			includedSchemas, err := myflags.GetStringArray(options.INCLUDE_SCHEMA)
   180  			Expect(err).ToNot(HaveOccurred())
   181  			Expect(includedSchemas).To(HaveLen(2))
   182  			Expect(includedSchemas[0]).To(Equal("myschema1"))
   183  			Expect(includedSchemas[1]).To(Equal("myschema2"))
   184  		})
   185  		It("it remembers flag values for INCLUDE_SCHEMA, EXCLUDE*, LEAF_PARTITION_DATA", func() {
   186  			err := myflags.Set(options.INCLUDE_SCHEMA, "my include schema")
   187  			Expect(err).ToNot(HaveOccurred())
   188  			err = myflags.Set(options.EXCLUDE_SCHEMA, "my exclude schema")
   189  			Expect(err).ToNot(HaveOccurred())
   190  			err = myflags.Set(options.LEAF_PARTITION_DATA, "true")
   191  			Expect(err).ToNot(HaveOccurred())
   192  
   193  			subject, err := options.NewOptions(myflags)
   194  			Expect(err).To(Not(HaveOccurred()))
   195  
   196  			Expect(subject.GetIncludedSchemas()[0]).To(Equal("my include schema"))
   197  			Expect(subject.GetExcludedSchemas()[0]).To(Equal("my exclude schema"))
   198  		})
   199  		It("returns an error upon invalid inclusions", func() {
   200  			err := myflags.Set(options.INCLUDE_RELATION, "foo")
   201  			Expect(err).ToNot(HaveOccurred())
   202  			_, err = options.NewOptions(myflags)
   203  			Expect(err).To(HaveOccurred())
   204  		})
   205  		Describe("AddIncludeRelation", func() {
   206  			It("it adds a relation", func() {
   207  				subject, err := options.NewOptions(myflags)
   208  				Expect(err).To(Not(HaveOccurred()))
   209  				subject.AddIncludedRelation("public.foobar")
   210  				Expect(subject.GetIncludedTables()).To(Equal([]string{"public.foobar"}))
   211  				Expect(subject.GetOriginalIncludedTables()).To(BeEmpty())
   212  			})
   213  		})
   214  	})
   215  	Describe("SeparateSchemaAndTable", func() {
   216  		It("properly splits the strings", func() {
   217  			tableList := []string{"foo.Bar", "FOO.Bar", "FO!@#.BAR"}
   218  			expectedFqn := []options.FqnStruct{
   219  				{SchemaName: `foo`, TableName: `Bar`},
   220  				{SchemaName: `FOO`, TableName: `Bar`},
   221  				{SchemaName: `FO!@#`, TableName: `BAR`},
   222  			}
   223  			resultFqn, err := options.SeparateSchemaAndTable(tableList)
   224  			Expect(err).ToNot(HaveOccurred())
   225  			Expect(resultFqn).To(Equal(expectedFqn))
   226  		})
   227  		It("fails to split TableName", func() {
   228  			tableList := []string{"foo."}
   229  			_, err := options.SeparateSchemaAndTable(tableList)
   230  			Expect(err).To(HaveOccurred())
   231  			Expect(err.Error()).To(ContainSubstring("foo."))
   232  		})
   233  		It("fails to split SchemaName", func() {
   234  			tableList := []string{".bar"}
   235  			_, err := options.SeparateSchemaAndTable(tableList)
   236  			Expect(err).To(HaveOccurred())
   237  			Expect(err.Error()).To(ContainSubstring(".bar"))
   238  		})
   239  		It("fails to split SchemaName or tableName (no '.')", func() {
   240  			tableList := []string{"foobar"}
   241  			_, err := options.SeparateSchemaAndTable(tableList)
   242  			Expect(err).To(HaveOccurred())
   243  			Expect(err.Error()).To(ContainSubstring("foobar"))
   244  		})
   245  		It("fails when there are more than one dots", func() {
   246  			// todo in a future story, establish a way for users to escape dots to show us which one is *in* the name versus the dot that divides schemaname from tablename
   247  			tableList := []string{"foobar.baz.bam"}
   248  			_, err := options.SeparateSchemaAndTable(tableList)
   249  			Expect(err).To(HaveOccurred())
   250  			Expect(err.Error()).To(ContainSubstring("foobar.baz.bam"))
   251  		})
   252  	})
   253  	Describe("QuoteTableNames", func() {
   254  		var (
   255  			conn   *dbconn.DBConn
   256  			mockdb sqlmock.Sqlmock
   257  		)
   258  		BeforeEach(func() {
   259  			conn, mockdb, _, _, _ = testhelper.SetupTestEnvironment()
   260  		})
   261  
   262  		It("returns empty result if given empty list", func() {
   263  			tablenames := make([]string, 0)
   264  			quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
   265  			Expect(err).To(Not(HaveOccurred()))
   266  			Expect(tablenames).To(Equal(quotedTableNames))
   267  		})
   268  		It("returns a single result when given a single fqn", func() {
   269  			tablenames := []string{"public.foo"}
   270  			queryMock := mockdb.ExpectQuery("SELECT quote_ident")
   271  			resultRows := sqlmock.NewRows([]string{"schemaname", "tablename"}).
   272  				AddRow("public", "foo")
   273  
   274  			queryMock.WillReturnRows(resultRows)
   275  
   276  			quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
   277  			Expect(err).To(Not(HaveOccurred()))
   278  			Expect(tablenames).To(Equal(quotedTableNames))
   279  		})
   280  		It("returns an array of correctly formatted fqn's", func() {
   281  			tablenames := []string{"public.one", "public.two", "public.three"}
   282  
   283  			queryMock := mockdb.ExpectQuery("SELECT quote_ident")
   284  			resultRows := sqlmock.NewRows([]string{"schemaname", "tablename"}).
   285  				AddRow("public", "one")
   286  			queryMock.WillReturnRows(resultRows)
   287  
   288  			queryMock = mockdb.ExpectQuery("SELECT quote_ident")
   289  			resultRows = sqlmock.NewRows([]string{"schemaname", "tablename"}).
   290  				AddRow("public", "two")
   291  			queryMock.WillReturnRows(resultRows)
   292  
   293  			queryMock = mockdb.ExpectQuery("SELECT quote_ident")
   294  			resultRows = sqlmock.NewRows([]string{"schemaname", "tablename"}).
   295  				AddRow("public", "three")
   296  			queryMock.WillReturnRows(resultRows)
   297  
   298  			quotedTableNames, err := options.QuoteTableNames(conn, tablenames)
   299  			Expect(err).To(Not(HaveOccurred()))
   300  			Expect(tablenames).To(Equal(quotedTableNames))
   301  		})
   302  		//	// todo handle embedded dots
   303  		//	PIt("handles dots within schema or tablename", func() {
   304  		//	})
   305  		//	// todo handle embedded commas
   306  		//	PIt("handles commas within schema or tablename", func() {
   307  		//	})
   308  		//	// todo handle embedded quotes
   309  		//	PIt("handles quotes within schema or tablename", func() {
   310  		//	})
   311  		//
   312  	})
   313  })