github.com/Elate-DevOps/migrate/v4@v4.0.12/database/sqlserver/sqlserver_test.go (about)

     1  package sqlserver
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	sqldriver "database/sql/driver"
     7  	"fmt"
     8  	"log"
     9  	"runtime"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/Elate-DevOps/migrate/v4"
    15  	"github.com/dhui/dktest"
    16  	"github.com/docker/go-connections/nat"
    17  
    18  	dt "github.com/Elate-DevOps/migrate/v4/database/testing"
    19  	"github.com/Elate-DevOps/migrate/v4/dktesting"
    20  
    21  	_ "github.com/Elate-DevOps/migrate/v4/source/file"
    22  )
    23  
    24  const (
    25  	defaultPort = 1433
    26  	saPassword  = "Root1234"
    27  )
    28  
    29  var (
    30  	sqlEdgeOpts = dktest.Options{
    31  		Env: map[string]string{"ACCEPT_EULA": "Y", "MSSQL_SA_PASSWORD": saPassword},
    32  		PortBindings: map[nat.Port][]nat.PortBinding{
    33  			nat.Port(fmt.Sprintf("%d/tcp", defaultPort)): {
    34  				nat.PortBinding{
    35  					HostIP:   "0.0.0.0",
    36  					HostPort: "0/tcp",
    37  				},
    38  			},
    39  		},
    40  		PortRequired: true, ReadyFunc: isReady, PullTimeout: 2 * time.Minute,
    41  	}
    42  	sqlServerOpts = dktest.Options{
    43  		Env:          map[string]string{"ACCEPT_EULA": "Y", "MSSQL_SA_PASSWORD": saPassword, "MSSQL_PID": "Express"},
    44  		PortRequired: true, ReadyFunc: isReady, PullTimeout: 2 * time.Minute,
    45  	}
    46  	// Container versions: https://mcr.microsoft.com/v2/mssql/server/tags/list
    47  	specs = []dktesting.ContainerSpec{
    48  		{ImageName: "mcr.microsoft.com/azure-sql-edge:latest", Options: sqlEdgeOpts},
    49  		{ImageName: "mcr.microsoft.com/mssql/server:2017-latest", Options: sqlServerOpts},
    50  		{ImageName: "mcr.microsoft.com/mssql/server:2019-latest", Options: sqlServerOpts},
    51  	}
    52  )
    53  
    54  func msConnectionString(host, port string) string {
    55  	return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master", saPassword, host, port)
    56  }
    57  
    58  func msConnectionStringMsiWithPassword(host, port string, useMsi bool) string {
    59  	return fmt.Sprintf("sqlserver://sa:%v@%v:%v?database=master&useMsi=%t", saPassword, host, port, useMsi)
    60  }
    61  
    62  func msConnectionStringMsi(host, port string, useMsi bool) string {
    63  	return fmt.Sprintf("sqlserver://sa@%v:%v?database=master&useMsi=%t", host, port, useMsi)
    64  }
    65  
    66  func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
    67  	ip, port, err := c.Port(defaultPort)
    68  	if err != nil {
    69  		return false
    70  	}
    71  	uri := msConnectionString(ip, port)
    72  	db, err := sql.Open("sqlserver", uri)
    73  	if err != nil {
    74  		return false
    75  	}
    76  	defer func() {
    77  		if err := db.Close(); err != nil {
    78  			log.Println("close error:", err)
    79  		}
    80  	}()
    81  	if err = db.PingContext(ctx); err != nil {
    82  		switch err {
    83  		case sqldriver.ErrBadConn:
    84  			return false
    85  		default:
    86  			fmt.Println(err)
    87  		}
    88  		return false
    89  	}
    90  
    91  	return true
    92  }
    93  
    94  func SkipIfUnsupportedArch(t *testing.T, c dktest.ContainerInfo) {
    95  	if strings.Contains(c.ImageName, "mssql") && !strings.HasPrefix(runtime.GOARCH, "amd") {
    96  		t.Skipf("Image %s is not supported on arch %s", c.ImageName, runtime.GOARCH)
    97  	}
    98  }
    99  
   100  func Test(t *testing.T) {
   101  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   102  		SkipIfUnsupportedArch(t, c)
   103  		ip, port, err := c.Port(defaultPort)
   104  		if err != nil {
   105  			t.Fatal(err)
   106  		}
   107  
   108  		addr := msConnectionString(ip, port)
   109  		p := &SQLServer{}
   110  		d, err := p.Open(addr)
   111  		if err != nil {
   112  			t.Fatalf("%v", err)
   113  		}
   114  
   115  		defer func() {
   116  			if err := d.Close(); err != nil {
   117  				t.Error(err)
   118  			}
   119  		}()
   120  
   121  		dt.Test(t, d, []byte("SELECT 1"))
   122  	})
   123  }
   124  
   125  func TestMigrate(t *testing.T) {
   126  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   127  		SkipIfUnsupportedArch(t, c)
   128  		ip, port, err := c.Port(defaultPort)
   129  		if err != nil {
   130  			t.Fatal(err)
   131  		}
   132  
   133  		addr := msConnectionString(ip, port)
   134  		p := &SQLServer{}
   135  		d, err := p.Open(addr)
   136  		if err != nil {
   137  			t.Fatalf("%v", err)
   138  		}
   139  
   140  		defer func() {
   141  			if err := d.Close(); err != nil {
   142  				t.Error(err)
   143  			}
   144  		}()
   145  
   146  		m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "master", d)
   147  		if err != nil {
   148  			t.Fatal(err)
   149  		}
   150  		dt.TestMigrate(t, m)
   151  	})
   152  }
   153  
   154  func TestMultiStatement(t *testing.T) {
   155  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   156  		SkipIfUnsupportedArch(t, c)
   157  		ip, port, err := c.Port(defaultPort)
   158  		if err != nil {
   159  			t.Fatal(err)
   160  		}
   161  
   162  		addr := msConnectionString(ip, port)
   163  		ms := &SQLServer{}
   164  		d, err := ms.Open(addr)
   165  		if err != nil {
   166  			t.Fatal(err)
   167  		}
   168  		defer func() {
   169  			if err := d.Close(); err != nil {
   170  				t.Error(err)
   171  			}
   172  		}()
   173  		if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);")); err != nil {
   174  			t.Fatalf("expected err to be nil, got %v", err)
   175  		}
   176  
   177  		// make sure second table exists
   178  		var exists int
   179  		if err := d.(*SQLServer).conn.QueryRowContext(context.Background(), "SELECT COUNT(1) FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT schema_name()) AND table_catalog = (SELECT db_name())").Scan(&exists); err != nil {
   180  			t.Fatal(err)
   181  		}
   182  		if exists != 1 {
   183  			t.Fatalf("expected table bar to exist")
   184  		}
   185  	})
   186  }
   187  
   188  func TestErrorParsing(t *testing.T) {
   189  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   190  		SkipIfUnsupportedArch(t, c)
   191  		ip, port, err := c.Port(defaultPort)
   192  		if err != nil {
   193  			t.Fatal(err)
   194  		}
   195  
   196  		addr := msConnectionString(ip, port)
   197  
   198  		p := &SQLServer{}
   199  		d, err := p.Open(addr)
   200  		if err != nil {
   201  			t.Fatal(err)
   202  		}
   203  		defer func() {
   204  			if err := d.Close(); err != nil {
   205  				t.Error(err)
   206  			}
   207  		}()
   208  
   209  		wantErr := `migration failed: Unknown object type 'TABLEE' used in a CREATE, DROP, or ALTER statement. in line 1:` +
   210  			` CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text); (details: mssql: Unknown object type ` +
   211  			`'TABLEE' used in a CREATE, DROP, or ALTER statement.)`
   212  		if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);")); err == nil {
   213  			t.Fatal("expected err but got nil")
   214  		} else if err.Error() != wantErr {
   215  			t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error())
   216  		}
   217  	})
   218  }
   219  
   220  func TestLockWorks(t *testing.T) {
   221  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   222  		SkipIfUnsupportedArch(t, c)
   223  		ip, port, err := c.Port(defaultPort)
   224  		if err != nil {
   225  			t.Fatal(err)
   226  		}
   227  
   228  		addr := fmt.Sprintf("sqlserver://sa:%v@%v:%v?master", saPassword, ip, port)
   229  		p := &SQLServer{}
   230  		d, err := p.Open(addr)
   231  		if err != nil {
   232  			t.Fatalf("%v", err)
   233  		}
   234  		dt.Test(t, d, []byte("SELECT 1"))
   235  
   236  		ms := d.(*SQLServer)
   237  
   238  		err = ms.Lock()
   239  		if err != nil {
   240  			t.Fatal(err)
   241  		}
   242  		err = ms.Unlock()
   243  		if err != nil {
   244  			t.Fatal(err)
   245  		}
   246  
   247  		// make sure the 2nd lock works (RELEASE_LOCK is very finicky)
   248  		err = ms.Lock()
   249  		if err != nil {
   250  			t.Fatal(err)
   251  		}
   252  		err = ms.Unlock()
   253  		if err != nil {
   254  			t.Fatal(err)
   255  		}
   256  	})
   257  }
   258  
   259  func TestMsiTrue(t *testing.T) {
   260  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   261  		SkipIfUnsupportedArch(t, c)
   262  		ip, port, err := c.Port(defaultPort)
   263  		if err != nil {
   264  			t.Fatal(err)
   265  		}
   266  
   267  		addr := msConnectionStringMsi(ip, port, true)
   268  		p := &SQLServer{}
   269  		_, err = p.Open(addr)
   270  		if err == nil {
   271  			t.Fatal("MSI should fail when not running in an Azure context.")
   272  		}
   273  	})
   274  }
   275  
   276  func TestOpenWithPasswordAndMSI(t *testing.T) {
   277  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   278  		SkipIfUnsupportedArch(t, c)
   279  		ip, port, err := c.Port(defaultPort)
   280  		if err != nil {
   281  			t.Fatal(err)
   282  		}
   283  
   284  		addr := msConnectionStringMsiWithPassword(ip, port, true)
   285  		p := &SQLServer{}
   286  		_, err = p.Open(addr)
   287  		if err == nil {
   288  			t.Fatal("Open should fail when both password and useMsi=true are passed.")
   289  		}
   290  
   291  		addr = msConnectionStringMsiWithPassword(ip, port, false)
   292  		p = &SQLServer{}
   293  		d, err := p.Open(addr)
   294  		if err != nil {
   295  			t.Fatal(err)
   296  		}
   297  
   298  		defer func() {
   299  			if err := d.Close(); err != nil {
   300  				t.Error(err)
   301  			}
   302  		}()
   303  
   304  		dt.Test(t, d, []byte("SELECT 1"))
   305  	})
   306  }
   307  
   308  func TestMsiFalse(t *testing.T) {
   309  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   310  		SkipIfUnsupportedArch(t, c)
   311  		ip, port, err := c.Port(defaultPort)
   312  		if err != nil {
   313  			t.Fatal(err)
   314  		}
   315  
   316  		addr := msConnectionStringMsi(ip, port, false)
   317  		p := &SQLServer{}
   318  		_, err = p.Open(addr)
   319  		if err == nil {
   320  			t.Fatal("Open should fail since no password was passed and useMsi is false.")
   321  		}
   322  	})
   323  }