github.com/bdollma-te/migrate/v4@v4.17.0-clickv2/database/rqlite/rqlite_test.go (about)

     1  package rqlite
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"testing"
    10  
    11  	"github.com/dhui/dktest"
    12  	"github.com/rqlite/gorqlite"
    13  	"github.com/stretchr/testify/assert"
    14  
    15  	"github.com/bdollma-te/migrate/v4"
    16  	dt "github.com/bdollma-te/migrate/v4/database/testing"
    17  	"github.com/bdollma-te/migrate/v4/dktesting"
    18  	_ "github.com/bdollma-te/migrate/v4/source/file"
    19  )
    20  
    21  var defaultPort uint16 = 4001
    22  
    23  var opts = dktest.Options{
    24  	Env:          map[string]string{"NODE_ID": "1"},
    25  	PortRequired: true,
    26  	ReadyFunc:    isReady,
    27  }
    28  var specs = []dktesting.ContainerSpec{
    29  	{ImageName: "rqlite/rqlite:7.21.4", Options: opts},
    30  	{ImageName: "rqlite/rqlite:8.0.6", Options: opts},
    31  	{ImageName: "rqlite/rqlite:8.11.1", Options: opts},
    32  	{ImageName: "rqlite/rqlite:8.12.3", Options: opts},
    33  }
    34  
    35  func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
    36  	ip, port, err := c.Port(defaultPort)
    37  	if err != nil {
    38  		fmt.Println("error getting port")
    39  		return false
    40  	}
    41  
    42  	statusString := fmt.Sprintf("http://%s:%s/status", ip, port)
    43  	fmt.Println(statusString)
    44  
    45  	var readyResp struct {
    46  		Store struct {
    47  			Ready bool `json:"ready"`
    48  		} `json:"store"`
    49  	}
    50  
    51  	resp, err := http.Get(statusString)
    52  	if err != nil {
    53  		fmt.Println("error getting status")
    54  		return false
    55  	}
    56  
    57  	if resp.StatusCode != 200 {
    58  		fmt.Println("statusCode != 200")
    59  		return false
    60  	}
    61  
    62  	body, err := io.ReadAll(resp.Body)
    63  	if err != nil {
    64  		fmt.Println("error reading body")
    65  		return false
    66  	}
    67  
    68  	if err := json.Unmarshal(body, &readyResp); err != nil {
    69  		fmt.Println("error unmarshaling body")
    70  		return false
    71  	}
    72  
    73  	return readyResp.Store.Ready
    74  }
    75  
    76  func Test(t *testing.T) {
    77  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
    78  		ip, port, err := c.Port(defaultPort)
    79  		assert.NoError(t, err)
    80  
    81  		connectString := fmt.Sprintf("rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true", ip, port)
    82  		t.Logf("DB connect string : %s\n", connectString)
    83  
    84  		r := &Rqlite{}
    85  		d, err := r.Open(connectString)
    86  		assert.NoError(t, err)
    87  
    88  		dt.Test(t, d, []byte("CREATE TABLE t (Qty int, Name string);"))
    89  	})
    90  }
    91  
    92  func TestMigrate(t *testing.T) {
    93  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
    94  		ip, port, err := c.Port(defaultPort)
    95  		assert.NoError(t, err)
    96  
    97  		connectString := fmt.Sprintf("rqlite://%s:%s?level=strong&disableClusterDiscovery=true&x-connect-insecure=true", ip, port)
    98  		t.Logf("DB connect string : %s\n", connectString)
    99  
   100  		driver, err := OpenURL(connectString)
   101  		assert.NoError(t, err)
   102  		defer func() {
   103  			if err := driver.Close(); err != nil {
   104  				return
   105  			}
   106  		}()
   107  
   108  		m, err := migrate.NewWithDatabaseInstance(
   109  			"file://./examples/migrations",
   110  			"ql", driver)
   111  		assert.NoError(t, err)
   112  
   113  		dt.TestMigrate(t, m)
   114  	})
   115  }
   116  
   117  func TestBadConnectInsecureParam(t *testing.T) {
   118  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   119  		ip, port, err := c.Port(defaultPort)
   120  		assert.NoError(t, err)
   121  
   122  		connectString := fmt.Sprintf("rqlite://%s:%s?x-connect-insecure=foo", ip, port)
   123  		t.Logf("DB connect string : %s\n", connectString)
   124  
   125  		_, err = OpenURL(connectString)
   126  		assert.ErrorIs(t, err, ErrBadConfig)
   127  	})
   128  }
   129  
   130  func TestBadProtocol(t *testing.T) {
   131  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   132  		ip, port, err := c.Port(defaultPort)
   133  		assert.NoError(t, err)
   134  
   135  		connectString := fmt.Sprintf("postgres://%s:%s/database", ip, port)
   136  		t.Logf("DB connect string : %s\n", connectString)
   137  
   138  		_, err = OpenURL(connectString)
   139  		assert.ErrorIs(t, err, ErrBadConfig)
   140  	})
   141  }
   142  
   143  func TestNoConfig(t *testing.T) {
   144  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   145  		ip, port, err := c.Port(defaultPort)
   146  		assert.NoError(t, err)
   147  
   148  		// gorqlite expects http(s) schemes
   149  		connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port)
   150  		t.Logf("DB connect string : %s\n", connectString)
   151  		db, err := gorqlite.Open(connectString)
   152  		assert.NoError(t, err)
   153  
   154  		_, err = WithInstance(db, nil)
   155  		assert.ErrorIs(t, err, ErrNilConfig)
   156  	})
   157  }
   158  
   159  func TestWithInstanceEmptyConfig(t *testing.T) {
   160  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   161  		ip, port, err := c.Port(defaultPort)
   162  		assert.NoError(t, err)
   163  
   164  		// gorqlite expects http(s) schemes
   165  		connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port)
   166  		t.Logf("DB connect string : %s\n", connectString)
   167  		db, err := gorqlite.Open(connectString)
   168  		assert.NoError(t, err)
   169  
   170  		driver, err := WithInstance(db, &Config{})
   171  		assert.NoError(t, err)
   172  
   173  		defer func() {
   174  			if err := driver.Close(); err != nil {
   175  				t.Fatal(err)
   176  			}
   177  		}()
   178  
   179  		m, err := migrate.NewWithDatabaseInstance(
   180  			"file://./examples/migrations",
   181  			"ql", driver)
   182  		assert.NoError(t, err)
   183  
   184  		t.Log("UP")
   185  		err = m.Up()
   186  		assert.NoError(t, err)
   187  
   188  		_, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", DefaultMigrationsTable))
   189  		assert.NoError(t, err)
   190  
   191  		t.Log("DOWN")
   192  		err = m.Down()
   193  		assert.NoError(t, err)
   194  	})
   195  }
   196  
   197  func TestMigrationTable(t *testing.T) {
   198  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   199  		ip, port, err := c.Port(defaultPort)
   200  		assert.NoError(t, err)
   201  
   202  		// gorqlite expects http(s) schemes
   203  		connectString := fmt.Sprintf("http://%s:%s?level=strong&disableClusterDiscovery=true", ip, port)
   204  		t.Logf("DB connect string : %s\n", connectString)
   205  		db, err := gorqlite.Open(connectString)
   206  		assert.NoError(t, err)
   207  
   208  		config := Config{MigrationsTable: "my_migration_table"}
   209  		driver, err := WithInstance(db, &config)
   210  		assert.NoError(t, err)
   211  
   212  		defer func() {
   213  			if err := driver.Close(); err != nil {
   214  				t.Fatal(err)
   215  			}
   216  		}()
   217  
   218  		m, err := migrate.NewWithDatabaseInstance(
   219  			"file://./examples/migrations",
   220  			"ql", driver)
   221  		assert.NoError(t, err)
   222  
   223  		t.Log("UP")
   224  		err = m.Up()
   225  		assert.NoError(t, err)
   226  
   227  		_, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", config.MigrationsTable))
   228  		assert.NoError(t, err)
   229  
   230  		_, err = db.WriteOne(`INSERT INTO pets (name, predator) VALUES ("franklin", true)`)
   231  		assert.NoError(t, err)
   232  
   233  		res, err := db.QueryOne(`SELECT name, predator FROM pets LIMIT 1`)
   234  		assert.NoError(t, err)
   235  
   236  		_ = res.Next()
   237  
   238  		// make sure we can use the migrated table
   239  		var petName string
   240  		var petPredator int
   241  		err = res.Scan(&petName, &petPredator)
   242  		assert.NoError(t, err)
   243  		assert.Equal(t, petName, "franklin")
   244  		assert.Equal(t, petPredator, 1)
   245  
   246  		t.Log("DOWN")
   247  		err = m.Down()
   248  		assert.NoError(t, err)
   249  
   250  		_, err = db.QueryOne(fmt.Sprintf("SELECT * FROM %s", config.MigrationsTable))
   251  		assert.NoError(t, err)
   252  	})
   253  }
   254  
   255  func TestParseUrl(t *testing.T) {
   256  	tests := []struct {
   257  		name           string
   258  		passedUrl      string
   259  		expectedUrl    string
   260  		expectedConfig *Config
   261  		expectedErr    string
   262  	}{
   263  		{
   264  			"defaults",
   265  			"rqlite://localhost:4001",
   266  			"https://localhost:4001",
   267  			&Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: DefaultMigrationsTable},
   268  			"",
   269  		},
   270  		{
   271  			"configure migration table",
   272  			"rqlite://localhost:4001?x-migrations-table=foo",
   273  			"https://localhost:4001",
   274  			&Config{ConnectInsecure: DefaultConnectInsecure, MigrationsTable: "foo"},
   275  			"",
   276  		},
   277  		{
   278  			"configure connect insecure",
   279  			"rqlite://localhost:4001?x-connect-insecure=true",
   280  			"http://localhost:4001",
   281  			&Config{ConnectInsecure: true, MigrationsTable: DefaultMigrationsTable},
   282  			"",
   283  		},
   284  		{
   285  			"invalid migration table",
   286  			"rqlite://localhost:4001?x-migrations-table=sqlite_bar",
   287  			"",
   288  			nil,
   289  			"invalid value for x-migrations-table: bad parameter",
   290  		},
   291  		{
   292  			"invalid connect insecure",
   293  			"rqlite://localhost:4001?x-connect-insecure=baz",
   294  			"",
   295  			nil,
   296  			"invalid value for x-connect-insecure: bad parameter",
   297  		},
   298  		{
   299  			"invalid url",
   300  			string([]byte{0x7f}),
   301  			"",
   302  			nil,
   303  			"parse \"\\x7f\": net/url: invalid control character in URL",
   304  		},
   305  	}
   306  	for _, tt := range tests {
   307  		t.Run(tt.name, func(t *testing.T) {
   308  			actualUrl, actualConfig, actualErr := parseUrl(tt.passedUrl)
   309  			if tt.expectedUrl != "" {
   310  				assert.Equal(t, tt.expectedUrl, actualUrl.String())
   311  			} else {
   312  				assert.Nil(t, actualUrl)
   313  			}
   314  
   315  			assert.Equal(t, tt.expectedConfig, actualConfig)
   316  
   317  			if tt.expectedErr == "" {
   318  				assert.NoError(t, actualErr)
   319  			} else {
   320  				assert.EqualError(t, actualErr, tt.expectedErr)
   321  			}
   322  		})
   323  	}
   324  }