Specs refactor and tweaks

- Make ParseOptions func to take an arg and return struct so we can test it
- Refactor and add more tests for options parsing
- Run test suite on postgres 10.x branch
- Change format for connection idle timer from float64 to in
This commit is contained in:
Dan Sosedoff 2018-02-22 14:20:18 -06:00
parent 6938eb5f35
commit 47500bf92e
8 changed files with 159 additions and 93 deletions

12
Godeps/Godeps.json generated
View File

@ -1,6 +1,6 @@
{
"ImportPath": "github.com/sosedoff/pgweb",
"GoVersion": "go1.9",
"GoVersion": "go1.10",
"GodepVersion": "v79",
"Packages": [
"./..."
@ -112,6 +112,16 @@
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "a5b47d31c556af34a302ce5d659e6fea44d90de0"
},
{
"ImportPath": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew",
"Comment": "v1.1.3",
"Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18"
},
{
"ImportPath": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib",
"Comment": "v1.1.3",
"Rev": "f390dcf405f7b83c997eac1b06768bb9f44dec18"
}
]
}

View File

@ -94,7 +94,7 @@ func initClient() {
}
func initOptions() {
err := command.ParseOptions()
opts, err := command.ParseOptions(os.Args)
if err != nil {
switch err.(type) {
case *flags.Error:
@ -104,8 +104,8 @@ func initOptions() {
}
os.Exit(1)
}
options = command.Opts
command.Opts = opts
options = opts
if options.Version {
printVersion()

View File

@ -367,7 +367,13 @@ func (client *Client) Close() error {
}
func (client *Client) IsIdle() bool {
return time.Since(client.lastQueryTime).Minutes() > command.Opts.ConnectionIdleTimeout
mins := int(time.Since(client.lastQueryTime).Minutes())
if command.Opts.ConnectionIdleTimeout > 0 {
return mins >= command.Opts.ConnectionIdleTimeout
}
return false
}
// Fetch all rows as strings for a single column

View File

@ -2,12 +2,16 @@ package client
import (
"fmt"
"log"
"os"
"os/exec"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/sosedoff/pgweb/pkg/command"
)
var (
@ -43,6 +47,11 @@ func getVar(name, def string) string {
}
func initVars() {
// We need to load default options to make sure all stuff works
if err := command.SetDefaultOptions(); err != nil {
log.Fatal(err)
}
serverHost = getVar("PGHOST", "localhost")
serverPort = getVar("PGPORT", "5432")
serverUser = getVar("PGUSER", "postgres")
@ -148,6 +157,21 @@ func test_NewClientFromUrl2(t *testing.T) {
assert.Equal(t, url, client.ConnectionString)
}
func test_ClientIdleTime(t *testing.T) {
examples := map[time.Time]bool{
time.Now(): false, // Current time
time.Now().Add(time.Minute * -30): false, // 30 minutes ago
time.Now().Add(time.Minute * -240): true, // 240 minutes ago
time.Now().Add(time.Minute * 30): false, // 30 minutes in future
time.Now().Add(time.Minute * 128): false, // 128 minutes in future
}
for ts, expected := range examples {
testClient.lastQueryTime = ts
assert.Equal(t, expected, testClient.IsIdle())
}
}
func test_Test(t *testing.T) {
assert.Equal(t, nil, testClient.Test())
}
@ -367,6 +391,7 @@ func TestAll(t *testing.T) {
setupClient()
test_NewClientFromUrl(t)
test_ClientIdleTime(t)
test_Test(t)
test_Info(t)
test_Activity(t)

View File

@ -9,86 +9,97 @@ import (
)
type Options struct {
Version bool `short:"v" long:"version" description:"Print version"`
Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"`
Url string `long:"url" description:"Database connection string"`
Host string `long:"host" description:"Server hostname or IP"`
Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"`
DbName string `long:"db" description:"Database name"`
Ssl string `long:"ssl" description:"SSL option"`
HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"`
HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"`
AuthUser string `long:"auth-user" description:"HTTP basic auth user"`
AuthPass string `long:"auth-pass" description:"HTTP basic auth password"`
SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"`
Sessions bool `long:"sessions" description:"Enable multiple database sessions" default:"false"`
Prefix string `long:"prefix" description:"Add a url prefix"`
ReadOnly bool `long:"readonly" description:"Run database connection in readonly mode"`
LockSession bool `long:"lock-session" description:"Lock session to a single database connection" default:"false"`
Bookmark string `short:"b" long:"bookmark" description:"Bookmark to use for connection. Bookmark files are stored under $HOME/.pgweb/bookmarks/*.toml" default:""`
BookmarksDir string `long:"bookmarks-dir" description:"Overrides default directory for bookmark files to search" default:""`
DisablePrettyJson bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export" default:"false"`
DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH" default:"false"`
ConnectBackend string `long:"connect-backend" description:"Enable database authentication through a third party backend"`
ConnectToken string `long:"connect-token" description:"Authentication token for the third-party connect backend"`
ConnectHeaders string `long:"connect-headers" description:"List of headers to pass to the connect backend"`
DisableConnectionIdleTimeout bool `long:"no-idle-timeout" description:"Disable connection idle timeout" default:"false"`
ConnectionIdleTimeout float64 `long:"idle-timeout" description:"Set connection idle timeout in minutes" default:"180"`
Cors bool `long:"cors" description:"Enable Cross-Origin Resource Sharing (CORS)" default:"false"`
CorsOrigin string `long:"cors-origin" description:"Allowed CORS origins" default:"*"`
Version bool `short:"v" long:"version" description:"Print version"`
Debug bool `short:"d" long:"debug" description:"Enable debugging mode" default:"false"`
Url string `long:"url" description:"Database connection string"`
Host string `long:"host" description:"Server hostname or IP"`
Port int `long:"port" description:"Server port" default:"5432"`
User string `long:"user" description:"Database user"`
Pass string `long:"pass" description:"Password for user"`
DbName string `long:"db" description:"Database name"`
Ssl string `long:"ssl" description:"SSL option"`
HttpHost string `long:"bind" description:"HTTP server host" default:"localhost"`
HttpPort uint `long:"listen" description:"HTTP server listen port" default:"8081"`
AuthUser string `long:"auth-user" description:"HTTP basic auth user"`
AuthPass string `long:"auth-pass" description:"HTTP basic auth password"`
SkipOpen bool `short:"s" long:"skip-open" description:"Skip browser open on start"`
Sessions bool `long:"sessions" description:"Enable multiple database sessions" default:"false"`
Prefix string `long:"prefix" description:"Add a url prefix"`
ReadOnly bool `long:"readonly" description:"Run database connection in readonly mode"`
LockSession bool `long:"lock-session" description:"Lock session to a single database connection" default:"false"`
Bookmark string `short:"b" long:"bookmark" description:"Bookmark to use for connection. Bookmark files are stored under $HOME/.pgweb/bookmarks/*.toml" default:""`
BookmarksDir string `long:"bookmarks-dir" description:"Overrides default directory for bookmark files to search" default:""`
DisablePrettyJson bool `long:"no-pretty-json" description:"Disable JSON formatting feature for result export" default:"false"`
DisableSSH bool `long:"no-ssh" description:"Disable database connections via SSH" default:"false"`
ConnectBackend string `long:"connect-backend" description:"Enable database authentication through a third party backend"`
ConnectToken string `long:"connect-token" description:"Authentication token for the third-party connect backend"`
ConnectHeaders string `long:"connect-headers" description:"List of headers to pass to the connect backend"`
DisableConnectionIdleTimeout bool `long:"no-idle-timeout" description:"Disable connection idle timeout" default:"false"`
ConnectionIdleTimeout int `long:"idle-timeout" description:"Set connection idle timeout in minutes" default:"180"`
Cors bool `long:"cors" description:"Enable Cross-Origin Resource Sharing (CORS)" default:"false"`
CorsOrigin string `long:"cors-origin" description:"Allowed CORS origins" default:"*"`
}
var Opts Options
func ParseOptions() error {
_, err := flags.ParseArgs(&Opts, os.Args)
func ParseOptions(args []string) (Options, error) {
var opts = Options{}
_, err := flags.ParseArgs(&opts, args)
if err != nil {
return err
return opts, err
}
if Opts.Url == "" {
Opts.Url = os.Getenv("DATABASE_URL")
if opts.Url == "" {
opts.Url = os.Getenv("DATABASE_URL")
}
if os.Getenv("SESSIONS") != "" {
Opts.Sessions = true
opts.Sessions = true
}
if os.Getenv("LOCK_SESSION") != "" {
Opts.LockSession = true
Opts.Sessions = false
opts.LockSession = true
opts.Sessions = false
}
if Opts.Prefix != "" && !strings.Contains(Opts.Prefix, "/") {
Opts.Prefix = Opts.Prefix + "/"
if opts.Prefix != "" && !strings.Contains(opts.Prefix, "/") {
opts.Prefix = opts.Prefix + "/"
}
if Opts.AuthUser == "" && os.Getenv("AUTH_USER") != "" {
Opts.AuthUser = os.Getenv("AUTH_USER")
if opts.AuthUser == "" && os.Getenv("AUTH_USER") != "" {
opts.AuthUser = os.Getenv("AUTH_USER")
}
if Opts.AuthPass == "" && os.Getenv("AUTH_PASS") != "" {
Opts.AuthPass = os.Getenv("AUTH_PASS")
if opts.AuthPass == "" && os.Getenv("AUTH_PASS") != "" {
opts.AuthPass = os.Getenv("AUTH_PASS")
}
if Opts.Bookmark != "" && Opts.Sessions {
return errors.New("--bookmark is not allowed in multi-session mode")
if opts.Bookmark != "" && opts.Sessions {
return opts, errors.New("--bookmark is not allowed in multi-session mode")
}
if Opts.ConnectBackend != "" {
if !Opts.Sessions {
return errors.New("--sessions flag must be set")
if opts.ConnectBackend != "" {
if !opts.Sessions {
return opts, errors.New("--sessions flag must be set")
}
if Opts.ConnectToken == "" {
return errors.New("--connect-token flag must be set")
if opts.ConnectToken == "" {
return opts, errors.New("--connect-token flag must be set")
}
} else {
if Opts.ConnectToken != "" || Opts.ConnectHeaders != "" {
return errors.New("--connect-backend flag must be set")
if opts.ConnectToken != "" || opts.ConnectHeaders != "" {
return opts, errors.New("--connect-backend flag must be set")
}
}
return opts, nil
}
func SetDefaultOptions() error {
opts, err := ParseOptions([]string{})
if err != nil {
return err
}
Opts = opts
return nil
}

View File

@ -1,38 +1,50 @@
package command
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Options(t *testing.T) {
err := ParseOptions()
func TestParseOptions(t *testing.T) {
// Test default behavior
opts, err := ParseOptions([]string{})
assert.NoError(t, err)
assert.Equal(t, false, opts.Sessions)
assert.Equal(t, "", opts.Prefix)
assert.Equal(t, "", opts.ConnectToken)
assert.Equal(t, "", opts.ConnectHeaders)
assert.Equal(t, false, opts.DisableSSH)
assert.Equal(t, false, opts.DisablePrettyJson)
assert.Equal(t, false, opts.DisableConnectionIdleTimeout)
assert.Equal(t, 180, opts.ConnectionIdleTimeout)
assert.Equal(t, false, opts.Cors)
assert.Equal(t, "*", opts.CorsOrigin)
// Test sessions
opts, err = ParseOptions([]string{"--sessions", "1"})
assert.NoError(t, err)
assert.Equal(t, true, opts.Sessions)
opts, err = ParseOptions([]string{"--sessions", "1", "--bookmark", "test"})
assert.EqualError(t, err, "--bookmark is not allowed in multi-session mode")
// Test url prefix
opts, err = ParseOptions([]string{"--prefix", "pgweb"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
opts, err = ParseOptions([]string{"--prefix", "pgweb/"})
assert.NoError(t, err)
assert.Equal(t, "pgweb/", opts.Prefix)
// Test connect backend options
opts, err = ParseOptions([]string{"--connect-backend", "test"})
assert.EqualError(t, err, "--sessions flag must be set")
opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions"})
assert.EqualError(t, err, "--connect-token flag must be set")
opts, err = ParseOptions([]string{"--connect-backend", "test", "--sessions", "--connect-token", "token"})
assert.NoError(t, err)
assert.Equal(t, false, Opts.Sessions)
assert.Equal(t, "", Opts.Prefix)
}
func Test_SessionsOption(t *testing.T) {
oldargs := os.Args
defer func() { os.Args = oldargs }()
os.Args = []string{"--sessions", "1"}
assert.NoError(t, ParseOptions())
assert.Equal(t, true, Opts.Sessions)
}
func Test_PrefixOption(t *testing.T) {
oldargs := os.Args
defer func() { os.Args = oldargs }()
os.Args = []string{"--prefix", "pgweb"}
assert.NoError(t, ParseOptions())
assert.Equal(t, "pgweb/", Opts.Prefix)
os.Args = []string{"--prefix", "pgweb/"}
assert.NoError(t, ParseOptions())
assert.Equal(t, "pgweb/", Opts.Prefix)
}

View File

@ -101,7 +101,7 @@ func staticCssAppCss() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/css/app.css", size: 11385, mode: os.FileMode(420), modTime: time.Unix(1509762282, 0)}
info := bindataFileInfo{name: "static/css/app.css", size: 11385, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -301,7 +301,7 @@ func staticIndexHtml() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/index.html", size: 12058, mode: os.FileMode(420), modTime: time.Unix(1513313307, 0)}
info := bindataFileInfo{name: "static/index.html", size: 12058, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@ -361,7 +361,7 @@ func staticJsAppJs() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "static/js/app.js", size: 34563, mode: os.FileMode(420), modTime: time.Unix(1512708944, 0)}
info := bindataFileInfo{name: "static/js/app.js", size: 34563, mode: os.FileMode(420), modTime: time.Unix(1517374041, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}

View File

@ -15,15 +15,17 @@ export PGPASSWORD=""
export PGDATABASE="booktown"
export PGPORT="15432"
for i in {1..6}
do
export PGVERSION="9.$i"
versions="9.1 9.2 9.3 9.4 9.5 9.6 10 10.1 10.2"
echo "---------------- BEGIN TEST ----------------"
for i in $versions
do
export PGVERSION="$i"
echo "------------------------------- BEGIN TEST -------------------------------"
echo "Running tests against PostgreSQL v$PGVERSION"
docker rm -f postgres || true
docker run -p $PGPORT:5432 --name postgres -e POSTGRES_PASSWORD=$PGPASSWORD -d postgres:$PGVERSION
sleep 5
make test
echo "---------------- END TEST ------------------"
echo "-------------------------------- END TEST --------------------------------"
done