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 }