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

database acceptance tests #7

Merged
merged 4 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ coverage.txt
*.out

.vscode
terraform-provider-snowflake
terraform-provider-snowflake

.DS_Store
.envrc
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
include:
- stage: check
script:
- make
- make test-acceptance
- bash <(curl -s https://codecov.io/bash)
- stage: check
script:
Expand Down
25 changes: 23 additions & 2 deletions Gopkg.lock

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

5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ test: ## run the tests
gotest -race -coverprofile=coverage.txt -covermode=atomic ./...
.PHONY: test

test-acceptance: ## runs all tests, including the acceptance tests which create and destroys real resources
TF_ACC=1 gotest -v -race -coverprofile=coverage.txt -covermode=atomic ./...
.PHONY: test-acceptance

install: ## install the terraform-provider-snowflake binary in $GOPATH/bin
go install ${LDFLAGS} .
.PHONY: install
Expand All @@ -60,7 +64,6 @@ install-tf: build ## installs plugin where terraform can find it
cp ./terraform-provider-snowflake $(HOME)/.terraform.d/plugins
.PHONY: install-tf


help: ## display help for this makefile
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: help
Expand Down
57 changes: 34 additions & 23 deletions pkg/resources/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import (
"database/sql"
"fmt"
"log"
"strconv"
"strings"

"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake"
"github.com/hashicorp/terraform/helper/schema"
"github.com/pkg/errors"
)

func Database() *schema.Resource {
d := newResourceDatabase()
return &schema.Resource{
Create: d.Create,
Read: d.Read,
Delete: d.Delete,
Update: d.Update,
Create: CreateDatabase,
Read: ReadDatabase,
Delete: DeleteDatabase,
Update: UpdateDatabase,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Expand All @@ -25,6 +26,9 @@ func Database() *schema.Resource {
ForceNew: false,
Description: "TODO",
ValidateFunc: ValidateDatabaseName,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return strings.ToUpper(old) == strings.ToUpper(new)
},
},
"comment": &schema.Schema{
Type: schema.TypeString,
Expand All @@ -35,32 +39,27 @@ func Database() *schema.Resource {
"data_retention_time_in_days": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
},
}
}

type database struct{}

func newResourceDatabase() *database {
return &database{}
}

func ValidateDatabaseName(val interface{}, key string) ([]string, []error) {
return snowflake.ValidateIdentifier(val)
}

func (d *database) Create(data *schema.ResourceData, meta interface{}) error {
func CreateDatabase(data *schema.ResourceData, meta interface{}) error {
name := data.Get("name").(string)
comment := data.Get("comment").(string)
retention := data.Get("data_retention_time_in_days")
retention, retentionSet := data.GetOk("data_retention_time_in_days")
db := meta.(*sql.DB)

// TODO prepared statements don't appear to work for DDL statements, so we might need to do all this ourselves
// TODO name appears to get normalized to uppercase, should we do that? or maybe just consider it
// case-insensitive?
stmt := fmt.Sprintf("CREATE DATABASE %s COMMENT='%s'", name, snowflake.EscapeString(comment))
if retention != nil {
if retentionSet {
stmt = fmt.Sprintf("%s DATA_RETENTION_TIME_IN_DAYS = %d", stmt, retention)
}
log.Printf("[DEBUG] stmt %s", stmt)
Expand All @@ -72,10 +71,10 @@ func (d *database) Create(data *schema.ResourceData, meta interface{}) error {

data.SetId(name)

return d.Read(data, meta)
return ReadDatabase(data, meta)
}

func (d *database) Read(data *schema.ResourceData, meta interface{}) error {
func ReadDatabase(data *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)

// TODO Not sure if we should use id or name here.
Expand All @@ -99,13 +98,25 @@ func (d *database) Read(data *schema.ResourceData, meta interface{}) error {
return errors.Wrap(err, "unable to scan row for SHOW DATABASES")
}

data.Set("name", dbname)
data.Set("comment", comment)
data.Set("data_retention_time_in_days", retentionTime)
return nil
err = data.Set("name", dbname.String)
if err != nil {
return err
}
err = data.Set("comment", comment.String)
if err != nil {
return err
}

i, err := strconv.ParseInt(retentionTime.String, 10, 64)
if err != nil {
return err
}

err = data.Set("data_retention_time_in_days", i)
return err
}

func (d *database) Delete(data *schema.ResourceData, meta interface{}) error {
func DeleteDatabase(data *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
name := data.Get("name").(string)

Expand All @@ -119,7 +130,7 @@ func (d *database) Delete(data *schema.ResourceData, meta interface{}) error {
return nil
}

func (d *database) Update(data *schema.ResourceData, meta interface{}) error {
func UpdateDatabase(data *schema.ResourceData, meta interface{}) error {
// https://www.terraform.io/docs/extend/writing-custom-providers.html#error-handling-amp-partial-state
data.Partial(true)

Expand Down Expand Up @@ -170,5 +181,5 @@ func (d *database) Update(data *schema.ResourceData, meta interface{}) error {
data.SetPartial("data_retention_time_in_days")
}
data.Partial(false)
return nil
return ReadDatabase(data, meta)
}
76 changes: 76 additions & 0 deletions pkg/resources/database_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package resources_test
edulop91 marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"
"strings"
"testing"

"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccDatabase(t *testing.T) {
prefix := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
prefix2 := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.Test(t, resource.TestCase{
Providers: providers(),
Steps: []resource.TestStep{
{
Config: dbConfig(prefix),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_database.db", "name", strings.ToUpper(prefix)),
resource.TestCheckResourceAttr("snowflake_database.db", "comment", "test comment"),
resource.TestCheckResourceAttrSet("snowflake_database.db", "data_retention_time_in_days"),
),
},
// RENAME
{
Config: dbConfig(prefix2),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_database.db", "name", strings.ToUpper(prefix2)),
resource.TestCheckResourceAttr("snowflake_database.db", "comment", "test comment"),
resource.TestCheckResourceAttrSet("snowflake_database.db", "data_retention_time_in_days"),
),
},
// CHANGE PROPERTIES
{
Config: dbConfig2(prefix2),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_database.db", "name", strings.ToUpper(prefix2)),
resource.TestCheckResourceAttr("snowflake_database.db", "comment", "test comment 2"),
resource.TestCheckResourceAttr("snowflake_database.db", "data_retention_time_in_days", "3"),
),
},
},
})
}

func dbConfig(prefix string) string {
s := `
resource "snowflake_database" "db" {
name = "%s"
comment = "test comment"
}
`
return fmt.Sprintf(s, prefix)
}
func dbConfig2(prefix string) string {
s := `
resource "snowflake_database" "db" {
name = "%s"
comment = "test comment 2"
data_retention_time_in_days = 3
}
`
return fmt.Sprintf(s, prefix)
}

func providers() map[string]terraform.ResourceProvider {
p := provider.Provider()
return map[string]terraform.ResourceProvider{
"snowflake": p,
}
}
54 changes: 54 additions & 0 deletions pkg/resources/database_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,66 @@
package resources_test

import (
"database/sql"
"testing"

"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider"
"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/resources"
"github.com/hashicorp/terraform/helper/schema"
"github.com/stretchr/testify/assert"
"gopkg.in/DATA-DOG/go-sqlmock.v1"
)

func TestDatabase(t *testing.T) {
resources.Database().InternalValidate(provider.Provider().Schema, false)
}

func TestDatabaseCreate(t *testing.T) {
a := assert.New(t)

in := map[string]interface{}{
"name": "good_name",
"comment": "great comment",
}
d := schema.TestResourceDataRaw(t, resources.Database().Schema, in)
a.NotNil(d)

withMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec("CREATE DATABASE good_name COMMENT='great comment").WillReturnResult(sqlmock.NewResult(1, 1))
expectRead(mock)
err := resources.CreateDatabase(d, db)
a.NoError(err)
})
}

func expectRead(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{"created_on", "name", "is_default", "is_current", "origin", "owner", "comment", "options", "retentionTime"}).AddRow("created_on", "good_name", "is_default", "is_current", "origin", "owner", "mock comment", "options", "1")
mock.ExpectQuery("SHOW DATABASES LIKE 'good_name'").WillReturnRows(rows)
}

func TestDatabaseRead(t *testing.T) {
a := assert.New(t)

d := database(t, "good_name", map[string]interface{}{"name": "good_name"})

withMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
expectRead(mock)
err := resources.ReadDatabase(d, db)
a.NoError(err)
a.Equal("good_name", d.Get("name").(string))
a.Equal("mock comment", d.Get("comment").(string))
a.Equal(1, d.Get("data_retention_time_in_days").(int))
})
}

func TestDatabaseDelete(t *testing.T) {
a := assert.New(t)

d := database(t, "drop_it", map[string]interface{}{"name": "drop_it"})

withMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec("DROP DATABASE drop_it").WillReturnResult(sqlmock.NewResult(1, 1))
err := resources.DeleteDatabase(d, db)
a.NoError(err)
})
}
Loading