Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/db-test #41

Merged
merged 8 commits into from
Mar 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,12 @@ git:

sudo: false

addons:
postgresql: "9.5"

install:
- psql -U postgres -c "CREATE DATABASE arborist_test"
- PGHOST=localhost PGPORT=5432 PGDATABASE=arborist_test PGUSER=postgres ./migrations/latest

script:
- go test -v ./...
73 changes: 73 additions & 0 deletions DEVELOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
### SQL

[This page](http://jmoiron.github.io/sqlx/) is a useful overview of `sqlx`
usage, the package which arborist uses for the database interface.

Be careful with `sql.DB` transactions; namely, be sure to close them if
returning early because of errors or similar, otherwise the transaction holds
its connection open. Similarly, when working with some `sql.Rows` always call
`.Close()` so the open connection is returned to the pool.

Go's SQL package handles the connection pool implicitly, though the size of the
pool is configurable. See [here](http://jmoiron.github.io/sqlx/#connectionPool)
for a bit more detail.

#### Migration Scripts

Reference previous `migrations` for examples on how to write migration scripts
correctly. The crucial points are

- Create a subdirectory in `migrations` named in the format
`{YYYY}-{MM}-{DD}T{HH}{MM}{SS}Z_{name}`, which is the ISO date format
followed optionally by a human-readable name describing the migration.
- This subdirectory must contain an `up.sql` and a `down.sql` which apply and
revert the migration, respectively.
- The `up.sql` script must *update* the singular row of `db_version` to
increment the integer version ID, and change the `version` text column to
reflect the exact folder name.

Test a migration by applying `up.sql` and `down.sql` sequentially to ensure
both work as expected.

### Testing

For testing an HTTP server, we use the `httptest` module to "record" requests
that we send to the handler for our server. The `httptest.ResponseRecorder`
stores the response information including `.Code` and `.Body` which can be
returned as a string or bytes.

This is a basic pattern for a test to hit a server endpoint (in this example,
sending some JSON in a `POST`):

```go
// arborist-specific
server := arborist.NewServer()
// ^ more setup for database, etc
logBuffer := bytes.NewBuffer([]byte{})
handler := server.MakeRouter(logBuffer)
// dump logBuffer to see server logs, if an error happens

// generic
w := httptest.NewRecorder()
req := newRequest("POST", "/some/endpoint", nil)
handler.ServeHTTP(w, req)
```

At this point we can inspect the recorder `w` for what we care about in the
response. Suppose we expect to get some JSON in the response from this request.
Our test would look something like this (here, we use the `testify/assert`
package for convenience):

```go
// one-off inline struct to read the response into
result := struct {
A string `json:"a"`
B int `json:"b"`
}{}
// Try to read response bytes into result JSON.
err := json.Unmarshal(w.Body.Bytes(), &result)
if err != nil {
t.Error("failed to read JSON")
}
assert.Equal(t, "what we expect", result.A, "result had the wrong value for a")
```
20 changes: 19 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@
[[constraint]]
name = "github.com/lib/pq.git"
version = "1.0.0"

[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
_default: bin/arborist

test: bin/arborist db-test
go test -v ./.../

bin/arborist:
go build -o bin/arborist

up: upgrade
upgrade:
./migrations/up

down: downgrade
downgrade:
./migrations/down

db-test: $(which psql)
-@ psql -c "CREATE DATABASE arborist_test" 2>&1 || true
./migrations/latest
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,11 @@ docker run -p 8080:8080 arborist --port 8080

## Development

For more details for developers, see `DEVELOP.md`.

### Tests

Run all the tests:
```bash
go test ./...
```

### Notes on Code

[This page](http://jmoiron.github.io/sqlx/) is a useful overview of `sqlx`
usage, the package which arborist uses for the database interface.

Be careful with `sql.DB` transactions; namely, be sure to close them if
returning early because of errors or similar, otherwise the transaction can
block future operations.
23 changes: 18 additions & 5 deletions arborist/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ type PolicyFromQuery struct {
}

func (policyFromQuery *PolicyFromQuery) standardize() *Policy {
paths := make([]string, len(policyFromQuery.ResourcePaths))
for i, queryPath := range policyFromQuery.ResourcePaths {
paths[i] = formatDbPath(queryPath)
}
policy := &Policy{
Name: policyFromQuery.Name,
ResourcePaths: policyFromQuery.ResourcePaths,
ResourcePaths: paths,
RoleIDs: policyFromQuery.RoleIDs,
}
if policyFromQuery.Description != nil {
Expand All @@ -96,11 +100,15 @@ func policyWithName(db *sqlx.DB, name string) (*PolicyFromQuery, error) {
GROUP BY policy.id
LIMIT 1
`
policy := PolicyFromQuery{}
err := db.Get(&policy, stmt, name)
policies := []PolicyFromQuery{}
err := db.Select(&policies, stmt, name)
if len(policies) == 0 {
return nil, nil
}
if err != nil {
return nil, err
}
policy := policies[0]
return &policy, nil
}

Expand Down Expand Up @@ -131,7 +139,11 @@ func listPoliciesFromDb(db *sqlx.DB) ([]PolicyFromQuery, error) {
// returned, resulted from the database operation.
func (policy *Policy) resources(db *sqlx.DB) ([]ResourceFromQuery, error) {
resources := []ResourceFromQuery{}
resourcesStmt := selectInStmt("resource", "ltree2text(path)", policy.ResourcePaths)
queryPaths := make([]string, len(policy.ResourcePaths))
for i, path := range policy.ResourcePaths {
queryPaths[i] = formatPathForDb(path)
}
resourcesStmt := selectInStmt("resource", "ltree2text(path)", queryPaths)
err := db.Select(&resources, resourcesStmt)
if err != nil {
return nil, err
Expand Down Expand Up @@ -201,7 +213,8 @@ func (policy *Policy) createInDb(db *sqlx.DB) *ErrorResponse {
// make sure all resources for new policy exist in DB
resourceSet := make(map[string]struct{})
for _, resource := range resources {
resourceSet[resource.Path] = struct{}{}
path := formatDbPath(resource.Path)
resourceSet[path] = struct{}{}
}
missingResources := []string{}
for _, path := range policy.ResourcePaths {
Expand Down
8 changes: 6 additions & 2 deletions arborist/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,15 @@ func roleWithName(db *sqlx.DB, name string) (*RoleFromQuery, error) {
GROUP BY role.id
LIMIT 1
`
role := RoleFromQuery{}
err := db.Get(&role, stmt, name)
roles := []RoleFromQuery{}
err := db.Select(&roles, stmt, name)
if len(roles) == 0 {
return nil, nil
}
if err != nil {
return nil, err
}
role := roles[0]
return &role, nil
}

Expand Down
Loading