Merge pull request #19 from antonlindstrom/refactor/cleanup
Refactor parts of the code and add more tests
This commit is contained in:
commit
0ea471cce0
10
.travis.yml
10
.travis.yml
|
@ -1,10 +1,14 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.2
|
- 1.5
|
||||||
|
- 1.6
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
script: make test
|
script:
|
||||||
|
- make get-deps
|
||||||
|
- make metalint
|
||||||
|
- make check
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- PGSTORE_TEST_CONN="postgres://postgres@127.0.0.1/test?sslmode=disable"
|
- PGSTORE_TEST_CONN="postgres://postgres@127.0.0.1/test?sslmode=disable"
|
||||||
|
@ -13,6 +17,6 @@ before_script:
|
||||||
- psql -c 'create database test;' -U postgres
|
- psql -c 'create database test;' -U postgres
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.3"
|
postgresql: "9.4"
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|
38
Makefile
38
Makefile
|
@ -1,13 +1,39 @@
|
||||||
all: get-deps build
|
all: get-deps build
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
@go build pgstore.go
|
go build ./...
|
||||||
|
|
||||||
|
.PHONY: get-deps
|
||||||
get-deps:
|
get-deps:
|
||||||
@go get -d -v ./...
|
go get -v ./...
|
||||||
|
|
||||||
test: get-deps
|
.PHONY: test
|
||||||
@go test -v ./...
|
test: get-deps metalint check
|
||||||
|
|
||||||
format:
|
.PHONY: check
|
||||||
@go fmt ./...
|
check:
|
||||||
|
go test -v -race -cover ./...
|
||||||
|
|
||||||
|
.PHONY: metalint
|
||||||
|
metalint:
|
||||||
|
which gometalinter > /dev/null || (go get github.com/alecthomas/gometalinter && gometalinter --install --update)
|
||||||
|
gometalinter --cyclo-over=20 -e "struct field Id should be ID" --enable="gofmt -s" --enable=misspell --fast ./...
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
@go fmt ./... | awk '{ print "Please run go fmt"; exit 1 }'
|
||||||
|
|
||||||
|
.PHONY: docker-test
|
||||||
|
docker-test:
|
||||||
|
docker run -d -p 5432:5432 --name=pgstore_test_1 postgres:9.4
|
||||||
|
sleep 5
|
||||||
|
docker run --rm --link pgstore_test_1:postgres postgres:9.4 psql -c 'create database test;' -U postgres -h postgres
|
||||||
|
PGSTORE_TEST_CONN="postgres://postgres@127.0.0.1:5432/test?sslmode=disable" make check
|
||||||
|
docker kill pgstore_test_1
|
||||||
|
docker rm pgstore_test_1
|
||||||
|
|
||||||
|
.PHONY: docker-clean
|
||||||
|
docker-clean:
|
||||||
|
-docker kill pgstore_test_1
|
||||||
|
-docker rm pgstore_test_1
|
||||||
|
|
|
@ -38,11 +38,11 @@ func (db *PGStore) cleanup(interval time.Duration, quit <-chan struct{}, done ch
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-quit:
|
case <-quit:
|
||||||
// Handle the quit signal
|
// Handle the quit signal.
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// Delete expired sessions on each tick
|
// Delete expired sessions on each tick.
|
||||||
err := db.deleteExpired()
|
err := db.deleteExpired()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("pgstore: unable to delete expired sessions: %v", err)
|
log.Printf("pgstore: unable to delete expired sessions: %v", err)
|
||||||
|
|
|
@ -8,14 +8,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCleanup(t *testing.T) {
|
func TestCleanup(t *testing.T) {
|
||||||
ss, err := NewPGStore(os.Getenv("PGSTORE_TEST_CONN"), []byte(secret))
|
dsn := os.Getenv("PGSTORE_TEST_CONN")
|
||||||
|
if dsn == "" {
|
||||||
if err != nil {
|
t.Skip("This test requires a real database.")
|
||||||
t.Fatal("Failed to get store", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss == nil {
|
ss, err := NewPGStore(dsn, []byte(secret))
|
||||||
t.Skip("This test requires a real database")
|
if err != nil {
|
||||||
|
t.Fatal("Failed to get store", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer ss.Close()
|
defer ss.Close()
|
||||||
|
@ -32,7 +32,7 @@ func TestCleanup(t *testing.T) {
|
||||||
t.Fatal("Failed to create session", err)
|
t.Fatal("Failed to create session", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expire the session
|
// Expire the session.
|
||||||
session.Options.MaxAge = 1
|
session.Options.MaxAge = 1
|
||||||
|
|
||||||
m := make(http.Header)
|
m := make(http.Header)
|
||||||
|
@ -40,8 +40,8 @@ func TestCleanup(t *testing.T) {
|
||||||
t.Fatal("failed to save session:", err.Error())
|
t.Fatal("failed to save session:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give the ticker a moment to run
|
// Give the ticker a moment to run.
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Millisecond * 1500)
|
||||||
|
|
||||||
// SELECT expired sessions. We should get a zero-length result slice back.
|
// SELECT expired sessions. We should get a zero-length result slice back.
|
||||||
var results []int64
|
var results []int64
|
||||||
|
|
45
pgstore.go
45
pgstore.go
|
@ -10,10 +10,12 @@ import (
|
||||||
"github.com/coopernurse/gorp"
|
"github.com/coopernurse/gorp"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
|
||||||
|
// Include the pq postgres driver.
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PGStore represents the currently configured session store
|
// PGStore represents the currently configured session store.
|
||||||
type PGStore struct {
|
type PGStore struct {
|
||||||
Codecs []securecookie.Codec
|
Codecs []securecookie.Codec
|
||||||
Options *sessions.Options
|
Options *sessions.Options
|
||||||
|
@ -36,7 +38,7 @@ type Session struct {
|
||||||
func NewPGStore(dbURL string, keyPairs ...[]byte) (*PGStore, error) {
|
func NewPGStore(dbURL string, keyPairs ...[]byte) (*PGStore, error) {
|
||||||
db, err := sql.Open("postgres", dbURL)
|
db, err := sql.Open("postgres", dbURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore and return nil
|
// Ignore PGStore value and return nil.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewPGStoreFromPool(db, keyPairs...)
|
return NewPGStoreFromPool(db, keyPairs...)
|
||||||
|
@ -62,14 +64,14 @@ func NewPGStoreFromPool(db *sql.DB, keyPairs ...[]byte) (*PGStore, error) {
|
||||||
err := dbmap.CreateTablesIfNotExists()
|
err := dbmap.CreateTablesIfNotExists()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore and return nil
|
// Ignore PGStore value and return nil.
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbStore, nil
|
return dbStore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the database connection
|
// Close closes the database connection.
|
||||||
func (db *PGStore) Close() {
|
func (db *PGStore) Close() {
|
||||||
db.DbMap.Db.Close()
|
db.DbMap.Db.Close()
|
||||||
}
|
}
|
||||||
|
@ -80,12 +82,13 @@ func (db *PGStore) Get(r *http.Request, name string) (*sessions.Session, error)
|
||||||
return sessions.GetRegistry(r).Get(db, name)
|
return sessions.GetRegistry(r).Get(db, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new session for the given name w/o adding it to the registry.
|
// New returns a new session for the given name without adding it to the registry.
|
||||||
func (db *PGStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
func (db *PGStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
||||||
session := sessions.NewSession(db, name)
|
session := sessions.NewSession(db, name)
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return session, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := *db.Options
|
opts := *db.Options
|
||||||
session.Options = &(opts)
|
session.Options = &(opts)
|
||||||
session.IsNew = true
|
session.IsNew = true
|
||||||
|
@ -121,7 +124,8 @@ func (db *PGStore) Save(r *http.Request, w http.ResponseWriter, session *session
|
||||||
// Generate a random session ID key suitable for storage in the DB
|
// Generate a random session ID key suitable for storage in the DB
|
||||||
session.ID = strings.TrimRight(
|
session.ID = strings.TrimRight(
|
||||||
base32.StdEncoding.EncodeToString(
|
base32.StdEncoding.EncodeToString(
|
||||||
securecookie.GenerateRandomKey(32)), "=")
|
securecookie.GenerateRandomKey(32),
|
||||||
|
), "=")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.save(session); err != nil {
|
if err := db.save(session); err != nil {
|
||||||
|
@ -165,39 +169,33 @@ func (db *PGStore) MaxAge(age int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// load fetches a session by ID from the database and decodes its content
|
// load fetches a session by ID from the database and decodes its content
|
||||||
// into session.Values
|
// into session.Values.
|
||||||
func (db *PGStore) load(session *sessions.Session) error {
|
func (db *PGStore) load(session *sessions.Session) error {
|
||||||
var s Session
|
var s Session
|
||||||
err := db.DbMap.SelectOne(&s, "SELECT * FROM http_sessions WHERE key = $1", session.ID)
|
err := db.DbMap.SelectOne(&s, "SELECT * FROM http_sessions WHERE key = $1", session.ID)
|
||||||
|
if err != nil {
|
||||||
if err := securecookie.DecodeMulti(session.Name(), string(s.Data),
|
|
||||||
&session.Values, db.Codecs...); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return securecookie.DecodeMulti(session.Name(), string(s.Data), &session.Values, db.Codecs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// save writes encoded session.Values to a database record.
|
// save writes encoded session.Values to a database record.
|
||||||
// writes to http_sessions table by default.
|
// writes to http_sessions table by default.
|
||||||
func (db *PGStore) save(session *sessions.Session) error {
|
func (db *PGStore) save(session *sessions.Session) error {
|
||||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, db.Codecs...)
|
||||||
db.Codecs...)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var createdOn time.Time
|
|
||||||
var expiresOn time.Time
|
|
||||||
|
|
||||||
crOn := session.Values["created_on"]
|
crOn := session.Values["created_on"]
|
||||||
exOn := session.Values["expires_on"]
|
exOn := session.Values["expires_on"]
|
||||||
|
|
||||||
if crOn == nil {
|
var expiresOn time.Time
|
||||||
|
|
||||||
|
createdOn, ok := crOn.(time.Time)
|
||||||
|
if !ok {
|
||||||
createdOn = time.Now()
|
createdOn = time.Now()
|
||||||
} else {
|
|
||||||
createdOn = crOn.(time.Time)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if exOn == nil {
|
if exOn == nil {
|
||||||
|
@ -218,11 +216,10 @@ func (db *PGStore) save(session *sessions.Session) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.IsNew {
|
if session.IsNew {
|
||||||
err = db.DbMap.Insert(&s)
|
return db.DbMap.Insert(&s)
|
||||||
} else {
|
|
||||||
_, err = db.DbMap.Exec("update http_sessions set data=$1, modified_on=$2, expires_on=$3 where key=$4", s.Data, s.ModifiedOn, s.ExpiresOn, s.Key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = db.DbMap.Exec("update http_sessions set data=$1, modified_on=$2, expires_on=$3 where key=$4", s.Data, s.ModifiedOn, s.ExpiresOn, s.Key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,14 @@ func (ho headerOnlyResponseWriter) WriteHeader(int) {
|
||||||
var secret = "EyaC2BPcJtNqU3tjEHy+c+Wmqc1yihYIbUWEl/jk0Ga73kWBclmuSFd9HuJKwJw/Wdsh1XnjY2Bw1HBVph6WOw=="
|
var secret = "EyaC2BPcJtNqU3tjEHy+c+Wmqc1yihYIbUWEl/jk0Ga73kWBclmuSFd9HuJKwJw/Wdsh1XnjY2Bw1HBVph6WOw=="
|
||||||
|
|
||||||
func TestPGStore(t *testing.T) {
|
func TestPGStore(t *testing.T) {
|
||||||
ss, err := NewPGStore(os.Getenv("PGSTORE_TEST_CONN"), []byte(secret))
|
dsn := os.Getenv("PGSTORE_TEST_CONN")
|
||||||
|
if dsn == "" {
|
||||||
if err != nil {
|
t.Skip("This test requires a real database.")
|
||||||
t.Fatal("failed to get store", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss == nil {
|
ss, err := NewPGStore(dsn, []byte(secret))
|
||||||
t.Skip("This test requires a real database")
|
if err != nil {
|
||||||
|
t.Fatal("Failed to get store", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer ss.Close()
|
defer ss.Close()
|
||||||
|
@ -125,14 +125,14 @@ func TestPGStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionOptionsAreUniquePerSession(t *testing.T) {
|
func TestSessionOptionsAreUniquePerSession(t *testing.T) {
|
||||||
ss, err := NewPGStore(os.Getenv("PGSTORE_TEST_CONN"), []byte(secret))
|
dsn := os.Getenv("PGSTORE_TEST_CONN")
|
||||||
|
if dsn == "" {
|
||||||
if err != nil {
|
t.Skip("This test requires a real database.")
|
||||||
t.Fatal("Failed to get store", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss == nil {
|
ss, err := NewPGStore(dsn, []byte(secret))
|
||||||
t.Skip("This test requires a real database")
|
if err != nil {
|
||||||
|
t.Fatal("Failed to get store", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer ss.Close()
|
defer ss.Close()
|
||||||
|
|
Loading…
Reference in New Issue