scopes

Paddy 2015-12-13 Parent:b93938562a17

2:a64a25ae2db1 tip Browse Files

Port over Postgres storage and tests. Do the minimum possible port of the code from auth to get Postgres working and make the tests run and pass again. This leaves a bug where the ErrScopeAlreadyExists type, when populatd from Postgres, contains the entire error string returned from the database, instead of parsing the ID itself out. Which is a thing we should do and add a test for.

memstore.go postgres.go scope.go scope_postgres.go scope_test.go types/scope.go types/scope_postgres.go

     1.1 --- a/memstore.go	Sat Dec 05 15:00:34 2015 -0800
     1.2 +++ b/memstore.go	Sun Dec 13 20:42:48 2015 -0800
     1.3 @@ -14,6 +14,12 @@
     1.4  	lock   sync.RWMutex
     1.5  }
     1.6  
     1.7 +func NewMemstore() *Memstore {
     1.8 +	return &Memstore{
     1.9 +		scopes: map[string]scopeTypes.Scope{},
    1.10 +	}
    1.11 +}
    1.12 +
    1.13  func (m *Memstore) CreateScopes(scopes []scopeTypes.Scope, ctx context.Context) error {
    1.14  	m.lock.Lock()
    1.15  	defer m.lock.Unlock()
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/postgres.go	Sun Dec 13 20:42:48 2015 -0800
     2.3 @@ -0,0 +1,17 @@
     2.4 +package scopes
     2.5 +
     2.6 +import (
     2.7 +	"database/sql"
     2.8 +)
     2.9 +
    2.10 +func NewPostgres(conn string) (postgres, error) {
    2.11 +	db, err := sql.Open("postgres", conn)
    2.12 +	if err != nil {
    2.13 +		return postgres{}, err
    2.14 +	}
    2.15 +	return postgres{db: db}, nil
    2.16 +}
    2.17 +
    2.18 +type postgres struct {
    2.19 +	db *sql.DB
    2.20 +}
     3.1 --- a/scope.go	Sat Dec 05 15:00:34 2015 -0800
     3.2 +++ b/scope.go	Sun Dec 13 20:42:48 2015 -0800
     3.3 @@ -20,14 +20,6 @@
     3.4  	return fmt.Sprintf("scope %s already exists", string(e))
     3.5  }
     3.6  
     3.7 -func stringsToScopes(s []string) scopeTypes.Scopes {
     3.8 -	res := make(scopeTypes.Scopes, len(s))
     3.9 -	for pos, scope := range s {
    3.10 -		res[pos] = scopeTypes.Scope{ID: scope}
    3.11 -	}
    3.12 -	return res
    3.13 -}
    3.14 -
    3.15  type Storer interface {
    3.16  	CreateScopes(scopes []scopeTypes.Scope, ctx context.Context) error
    3.17  	GetScopes(ids []string, ctx context.Context) (map[string]scopeTypes.Scope, error)
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/scope_postgres.go	Sun Dec 13 20:42:48 2015 -0800
     4.3 @@ -0,0 +1,157 @@
     4.4 +package scopes
     4.5 +
     4.6 +import (
     4.7 +	"code.secondbit.org/scopes.hg/types"
     4.8 +	"golang.org/x/net/context"
     4.9 +
    4.10 +	"github.com/lib/pq"
    4.11 +	"github.com/secondbit/pan"
    4.12 +)
    4.13 +
    4.14 +func (p *postgres) createScopesSQL(scopes []scopeTypes.Scope) *pan.Query {
    4.15 +	fields, _ := pan.GetFields(scopes[0])
    4.16 +	query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(scopes[0]))
    4.17 +	query.Include("(" + pan.QueryList(fields) + ")")
    4.18 +	query.Include("VALUES")
    4.19 +	query.FlushExpressions(" ")
    4.20 +	for _, scope := range scopes {
    4.21 +		_, values := pan.GetFields(scope)
    4.22 +		query.Include("("+pan.VariableList(len(values))+")", values...)
    4.23 +	}
    4.24 +	return query.FlushExpressions(", ")
    4.25 +}
    4.26 +
    4.27 +func (p *postgres) CreateScopes(scopes []scopeTypes.Scope, ctx context.Context) error {
    4.28 +	if len(scopes) < 1 {
    4.29 +		return nil
    4.30 +	}
    4.31 +	query := p.createScopesSQL(scopes)
    4.32 +	_, err := p.db.Exec(query.String(), query.Args...)
    4.33 +	if e, ok := err.(*pq.Error); ok && e.Constraint == "scopes_pkey" {
    4.34 +		// BUG(paddy): we need to parse e.Detail to pull out the duplicate Scope ID
    4.35 +		err = ErrScopeAlreadyExists(e.Detail)
    4.36 +	}
    4.37 +	return err
    4.38 +}
    4.39 +
    4.40 +func (p *postgres) getScopesSQL(ids []string) *pan.Query {
    4.41 +	var scope scopeTypes.Scope
    4.42 +	intids := make([]interface{}, len(ids))
    4.43 +	for pos, id := range ids {
    4.44 +		intids[pos] = id
    4.45 +	}
    4.46 +	fields, _ := pan.GetFields(scope)
    4.47 +	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
    4.48 +	query.IncludeWhere()
    4.49 +	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
    4.50 +	query.Include("("+pan.VariableList(len(ids))+")", intids...)
    4.51 +	return query.FlushExpressions(" ")
    4.52 +}
    4.53 +
    4.54 +func (p *postgres) GetScopes(ids []string, ctx context.Context) (map[string]scopeTypes.Scope, error) {
    4.55 +	query := p.getScopesSQL(ids)
    4.56 +	rows, err := p.db.Query(query.String(), query.Args...)
    4.57 +	if err != nil {
    4.58 +		return map[string]scopeTypes.Scope{}, err
    4.59 +	}
    4.60 +	scopes := map[string]scopeTypes.Scope{}
    4.61 +	for rows.Next() {
    4.62 +		var scope scopeTypes.Scope
    4.63 +		err := pan.Unmarshal(rows, &scope)
    4.64 +		if err != nil {
    4.65 +			return scopes, err
    4.66 +		}
    4.67 +		scopes[scope.ID] = scope
    4.68 +	}
    4.69 +	if err = rows.Err(); err != nil {
    4.70 +		return scopes, err
    4.71 +	}
    4.72 +	return scopes, nil
    4.73 +}
    4.74 +
    4.75 +func (p *postgres) updateScopeSQL(change scopeTypes.ScopeChange) *pan.Query {
    4.76 +	var scope scopeTypes.Scope
    4.77 +	query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(scope)+" SET ")
    4.78 +	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Name")+" = ?", change.Name)
    4.79 +	query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Description")+" = ?", change.Description)
    4.80 +	query.FlushExpressions(", ")
    4.81 +	query.IncludeWhere()
    4.82 +	query.Include(pan.GetUnquotedColumn(scope, "ID")+" = ?", change.ScopeID)
    4.83 +	return query.FlushExpressions(" ")
    4.84 +}
    4.85 +
    4.86 +func (p *postgres) UpdateScope(change scopeTypes.ScopeChange, ctx context.Context) error {
    4.87 +	if change.Empty() {
    4.88 +		return nil
    4.89 +	}
    4.90 +	query := p.updateScopeSQL(change)
    4.91 +	res, err := p.db.Exec(query.String(), query.Args...)
    4.92 +	if err != nil {
    4.93 +		return err
    4.94 +	}
    4.95 +	rows, err := res.RowsAffected()
    4.96 +	if err != nil {
    4.97 +		return err
    4.98 +	}
    4.99 +	if rows < 1 {
   4.100 +		return ErrScopeNotFound
   4.101 +	}
   4.102 +	return err
   4.103 +}
   4.104 +
   4.105 +func (p *postgres) removeScopesSQL(ids []string) *pan.Query {
   4.106 +	var scope scopeTypes.Scope
   4.107 +	intids := make([]interface{}, len(ids))
   4.108 +	for pos, id := range ids {
   4.109 +		intids[pos] = id
   4.110 +	}
   4.111 +	query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(scope))
   4.112 +	query.IncludeWhere()
   4.113 +	query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN")
   4.114 +	query.Include("("+pan.VariableList(len(ids))+")", intids...)
   4.115 +	return query.FlushExpressions(" ")
   4.116 +}
   4.117 +
   4.118 +func (p *postgres) RemoveScopes(ids []string, ctx context.Context) error {
   4.119 +	query := p.removeScopesSQL(ids)
   4.120 +	res, err := p.db.Exec(query.String(), query.Args...)
   4.121 +	if err != nil {
   4.122 +		return err
   4.123 +	}
   4.124 +	rows, err := res.RowsAffected()
   4.125 +	if err != nil {
   4.126 +		return err
   4.127 +	}
   4.128 +	if rows < 1 {
   4.129 +		return ErrScopeNotFound
   4.130 +	}
   4.131 +	return nil
   4.132 +}
   4.133 +
   4.134 +func (p *postgres) listScopesSQL() *pan.Query {
   4.135 +	var scope scopeTypes.Scope
   4.136 +	fields, _ := pan.GetFields(scope)
   4.137 +	query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope))
   4.138 +	return query.FlushExpressions(" ")
   4.139 +}
   4.140 +
   4.141 +func (p *postgres) ListScopes(ctx context.Context) ([]scopeTypes.Scope, error) {
   4.142 +	query := p.listScopesSQL()
   4.143 +	rows, err := p.db.Query(query.String(), query.Args...)
   4.144 +	if err != nil {
   4.145 +		return []scopeTypes.Scope{}, err
   4.146 +	}
   4.147 +	var scopes []scopeTypes.Scope
   4.148 +	for rows.Next() {
   4.149 +		var scope scopeTypes.Scope
   4.150 +		err = pan.Unmarshal(rows, &scope)
   4.151 +		if err != nil {
   4.152 +			return scopes, err
   4.153 +		}
   4.154 +		scopes = append(scopes, scope)
   4.155 +	}
   4.156 +	if err = rows.Err(); err != nil {
   4.157 +		return scopes, err
   4.158 +	}
   4.159 +	return scopes, nil
   4.160 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/scope_test.go	Sun Dec 13 20:42:48 2015 -0800
     5.3 @@ -0,0 +1,167 @@
     5.4 +package scopes
     5.5 +
     5.6 +import (
     5.7 +	"os"
     5.8 +
     5.9 +	"golang.org/x/net/context"
    5.10 +
    5.11 +	"code.secondbit.org/scopes.hg/types"
    5.12 +)
    5.13 +import "testing"
    5.14 +
    5.15 +func init() {
    5.16 +	if os.Getenv("PG_TEST_DB") != "" {
    5.17 +		p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
    5.18 +		if err != nil {
    5.19 +			panic(err)
    5.20 +		}
    5.21 +		storers = append(storers, &p)
    5.22 +	}
    5.23 +}
    5.24 +
    5.25 +var storers = []Storer{NewMemstore()}
    5.26 +
    5.27 +func compareScopes(scope1, scope2 scopeTypes.Scope) (success bool, field string, val1, val2 interface{}) {
    5.28 +	if scope1.ID != scope2.ID {
    5.29 +		return false, "ID", scope1.ID, scope2.ID
    5.30 +	}
    5.31 +	if scope1.Name != scope2.Name {
    5.32 +		return false, "Name", scope1.Name, scope2.Name
    5.33 +	}
    5.34 +	if scope1.Description != scope2.Description {
    5.35 +		return false, "Description", scope1.Description, scope2.Description
    5.36 +	}
    5.37 +	return true, "", nil, nil
    5.38 +}
    5.39 +
    5.40 +func TestScopeInScopeStore(t *testing.T) {
    5.41 +	scope := scopeTypes.Scope{
    5.42 +		ID:          "testscope",
    5.43 +		Name:        "Test Scope",
    5.44 +		Description: "Access to testing data.",
    5.45 +	}
    5.46 +	scope2 := scopeTypes.Scope{
    5.47 +		ID:          "testscope2",
    5.48 +		Name:        "Test Scope 2",
    5.49 +		Description: "Access to minions.",
    5.50 +	}
    5.51 +	scope3 := scopeTypes.Scope{
    5.52 +		ID:          "testscope3",
    5.53 +		Name:        "Test Scope 3",
    5.54 +		Description: "Access to bananas.",
    5.55 +	}
    5.56 +	updatedName := "Updated Scope"
    5.57 +	updatedDescription := "An updated scope."
    5.58 +	update := scopeTypes.ScopeChange{
    5.59 +		ScopeID: scope.ID,
    5.60 +		Name:    &updatedName,
    5.61 +	}
    5.62 +	update2 := scopeTypes.ScopeChange{
    5.63 +		ScopeID:     scope2.ID,
    5.64 +		Description: &updatedDescription,
    5.65 +	}
    5.66 +	update3 := scopeTypes.ScopeChange{
    5.67 +		ScopeID:     scope3.ID,
    5.68 +		Name:        &updatedName,
    5.69 +		Description: &updatedDescription,
    5.70 +	}
    5.71 +	for _, store := range storers {
    5.72 +		ctx := context.Background()
    5.73 +		retrieved, err := store.GetScopes([]string{scope.ID}, ctx)
    5.74 +		if len(retrieved) != 0 {
    5.75 +			t.Logf("%+v", retrieved)
    5.76 +			t.Errorf("Expected %d results, got %d from %T", 0, len(retrieved), store)
    5.77 +		}
    5.78 +		err = store.CreateScopes([]scopeTypes.Scope{scope}, ctx)
    5.79 +		if err != nil {
    5.80 +			t.Errorf("Error saving scope to %T: %s", store, err)
    5.81 +		}
    5.82 +		err = store.CreateScopes([]scopeTypes.Scope{scope}, ctx)
    5.83 +		if _, ok := err.(ErrScopeAlreadyExists); !ok {
    5.84 +			t.Errorf("Expected ErrScopeAlreadyExists, got %s instead for %T", err, store)
    5.85 +		}
    5.86 +		retrieved, err = store.GetScopes([]string{scope.ID}, ctx)
    5.87 +		if err != nil {
    5.88 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
    5.89 +		}
    5.90 +		if len(retrieved) != 1 {
    5.91 +			t.Logf("%+v", retrieved)
    5.92 +			t.Errorf("Expected %d results, got %d from %T", 1, len(retrieved), store)
    5.93 +		}
    5.94 +		if _, ok := retrieved[scope.ID]; !ok {
    5.95 +			t.Logf("%+v", retrieved)
    5.96 +			t.Errorf("Expected %s to be in results from %T, wasn't", scope.ID, store)
    5.97 +		}
    5.98 +		success, field, val1, val2 := compareScopes(scope, retrieved[scope.ID])
    5.99 +		if !success {
   5.100 +			t.Errorf("Expected %s to be %+v, got %+v from %T", field, val1, val2, store)
   5.101 +		}
   5.102 +		err = store.CreateScopes([]scopeTypes.Scope{scope2, scope3}, ctx)
   5.103 +		if err != nil {
   5.104 +			t.Errorf("Unexpected error trying to create scope2 and scope3 in %T: %+v", store, err)
   5.105 +		}
   5.106 +		retrieved, err = store.GetScopes([]string{scope.ID, scope2.ID, scope3.ID}, ctx)
   5.107 +		if err != nil {
   5.108 +			t.Errorf("Unexpected error retrieving scopes from  %T: %+v", store, err)
   5.109 +		}
   5.110 +		if len(retrieved) != 3 {
   5.111 +			t.Logf("%+v", retrieved)
   5.112 +			t.Errorf("Expected %d results, got %d from %T", 3, len(retrieved), store)
   5.113 +		}
   5.114 +		for _, s := range []scopeTypes.Scope{scope, scope2, scope3} {
   5.115 +			if _, ok := retrieved[s.ID]; !ok {
   5.116 +				t.Logf("%+v", retrieved)
   5.117 +				t.Errorf("Expected %s to be in results from %T, wasn't", s.ID, store)
   5.118 +			}
   5.119 +			success, field, val1, val2 = compareScopes(s, retrieved[s.ID])
   5.120 +			if !success {
   5.121 +				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
   5.122 +			}
   5.123 +		}
   5.124 +		err = store.UpdateScope(update, ctx)
   5.125 +		if err != nil {
   5.126 +			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
   5.127 +		}
   5.128 +		scope = scopeTypes.ApplyChange(update, scope)
   5.129 +		err = store.UpdateScope(update2, ctx)
   5.130 +		if err != nil {
   5.131 +			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
   5.132 +		}
   5.133 +		scope2 = scopeTypes.ApplyChange(update2, scope2)
   5.134 +		err = store.UpdateScope(update3, ctx)
   5.135 +		if err != nil {
   5.136 +			t.Errorf("Unexpected error updating scope in %T: %+v", store, err)
   5.137 +		}
   5.138 +		scope3 = scopeTypes.ApplyChange(update3, scope3)
   5.139 +		results, err := store.ListScopes(ctx)
   5.140 +		if err != nil {
   5.141 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
   5.142 +		}
   5.143 +		if len(results) != 3 {
   5.144 +			t.Logf("%+v", results)
   5.145 +			t.Errorf("Expected %d results, got %d from %T", 3, len(results), store)
   5.146 +		}
   5.147 +		for pos, s := range []scopeTypes.Scope{scope, scope2, scope3} {
   5.148 +			success, field, val1, val2 = compareScopes(s, results[pos])
   5.149 +			if !success {
   5.150 +				t.Errorf("Expected %s to be %+v for scope %s, got %+v from %T", field, val1, s.ID, val2, store)
   5.151 +			}
   5.152 +		}
   5.153 +		err = store.RemoveScopes([]string{scope.ID, scope2.ID, scope3.ID}, ctx)
   5.154 +		if err != nil {
   5.155 +			t.Errorf("Unexpected error removing scopes from %T: %+v", store, err)
   5.156 +		}
   5.157 +		results, err = store.ListScopes(ctx)
   5.158 +		if err != nil {
   5.159 +			t.Errorf("Unexpected error retrieving scopes from %T: %+v", store, err)
   5.160 +		}
   5.161 +		if len(results) != 0 {
   5.162 +			t.Logf("%+v", results)
   5.163 +			t.Errorf("Expected %d results, got %d from %T", 0, len(results), store)
   5.164 +		}
   5.165 +		err = store.UpdateScope(update, ctx)
   5.166 +		if err != ErrScopeNotFound {
   5.167 +			t.Errorf("Unexpected error updating non-existent scopes from %T: %+v", store, err)
   5.168 +		}
   5.169 +	}
   5.170 +}
     6.1 --- a/types/scope.go	Sat Dec 05 15:00:34 2015 -0800
     6.2 +++ b/types/scope.go	Sun Dec 13 20:42:48 2015 -0800
     6.3 @@ -50,3 +50,11 @@
     6.4  func (s ScopeChange) Empty() bool {
     6.5  	return s.Name == nil && s.Description == nil
     6.6  }
     6.7 +
     6.8 +func StringsToScopes(s []string) Scopes {
     6.9 +	res := make(Scopes, len(s))
    6.10 +	for pos, scope := range s {
    6.11 +		res[pos] = Scope{ID: scope}
    6.12 +	}
    6.13 +	return res
    6.14 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/types/scope_postgres.go	Sun Dec 13 20:42:48 2015 -0800
     7.3 @@ -0,0 +1,32 @@
     7.4 +package scopeTypes
     7.5 +
     7.6 +import (
     7.7 +	"database/sql/driver"
     7.8 +
     7.9 +	"code.secondbit.org/pqarrays.hg"
    7.10 +)
    7.11 +
    7.12 +func (s Scope) GetSQLTableName() string {
    7.13 +	return "scopes"
    7.14 +}
    7.15 +
    7.16 +func (s Scopes) Value() (driver.Value, error) {
    7.17 +	ids := make(pqarrays.StringArray, 0, len(s))
    7.18 +	for _, scope := range s {
    7.19 +		ids = append(ids, scope.ID)
    7.20 +	}
    7.21 +	return ids.Value()
    7.22 +}
    7.23 +
    7.24 +func (s *Scopes) Scan(value interface{}) error {
    7.25 +	*s = (*s)[:0]
    7.26 +	var ids pqarrays.StringArray
    7.27 +	err := ids.Scan(value)
    7.28 +	if err != nil {
    7.29 +		return err
    7.30 +	}
    7.31 +	for _, id := range ids {
    7.32 +		*s = append(*s, Scope{ID: id})
    7.33 +	}
    7.34 +	return nil
    7.35 +}