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