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 }