blob: 61be5fdde29dc30c5389eff68cc107f82e267f8e [file]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
)
const goodConfig = `
databases:
# Comment, blah.
- id: dev
user: ${user}
db: dev-${user}
cloud_sql_instance: cloud-project-dev:us-central1:sql-db-dev
local_socket: /var/tmp/cloud-sql/cloud-project-dev:us-central1:sql-db-dev
- id: staging
user: root
db: staging
local_socket: /var/tmp/cloud-sql/cloud-project-dev:us-central1:sql-db-dev
- id: prod
user: root
db: prod
cloud_sql_instance: cloud-project-prod:us-central1:sql-db-prod
require_password: true
`
func dropFile(body string) (string, func(), error) {
f, err := ioutil.TempFile("", "cloudsqlhelper")
if err != nil {
return "", nil, err
}
cleanup := func() {
f.Close()
os.Remove(f.Name())
}
if _, err := f.WriteString(body); err != nil {
cleanup()
return "", nil, err
}
f.Close()
return f.Name(), cleanup, nil
}
func TestReadConfigHappyPath(t *testing.T) {
path, cleanup, err := dropFile(goodConfig)
if err != nil {
t.Fatal(err)
}
defer cleanup()
cfg, err := ReadConfig(path, map[string]string{"user": "os_user"})
if err != nil {
t.Fatal(err)
}
expected := &Config{
Databases: []*DBConfig{
{
ID: "dev",
User: "os_user",
DB: "dev-os_user",
CloudSQLInstance: "cloud-project-dev:us-central1:sql-db-dev",
LocalSocket: "/var/tmp/cloud-sql/cloud-project-dev:us-central1:sql-db-dev",
},
{
ID: "staging",
User: "root",
DB: "staging",
LocalSocket: "/var/tmp/cloud-sql/cloud-project-dev:us-central1:sql-db-dev",
},
{
ID: "prod",
User: "root",
DB: "prod",
CloudSQLInstance: "cloud-project-prod:us-central1:sql-db-prod",
RequirePassword: true,
},
},
}
if !reflect.DeepEqual(cfg, expected) {
t.Errorf("%v != %v", cfg, expected)
}
}
func TestReadConfigMissing(t *testing.T) {
if _, err := ReadConfig(filepath.Join(os.TempDir(), "some-random-missing-path"), nil); err == nil {
t.Fatal("should have failed, but it didn't")
}
}
func TestReadConfigBroken(t *testing.T) {
path, cleanup, err := dropFile("not a yaml")
if err != nil {
t.Fatal(err)
}
defer cleanup()
if _, err := ReadConfig(path, nil); err == nil {
t.Fatal("should have failed, but it didn't")
}
}
func TestReadConfigValidation(t *testing.T) {
cases := []struct {
cfg string
err string
}{
{
`{"databases":[{"id": "dev"}]}`,
`bad config - in "dev", 'user' is required`,
},
{
`{"databases":[{"id": "dev", "user": "blah"}]}`,
`bad config - in "dev", 'db' is required`,
},
{
`{"databases":[{"id": "dev", "user": "blah", "db": "blah"}]}`,
`bad config - in "dev", 'local_socket' is required if 'cloud_sql_instance' is not set`,
},
{
`{"databases":[{"id": "dev", "user": "blah", "db": "blah"}]}`,
`bad config - in "dev", 'local_socket' is required if 'cloud_sql_instance' is not set`,
},
{
`{"databases":[{"id": "dev", "user": "blah", "db": "blah", "cloud_sql_instance": "zzzz"}]}`,
`bad config - in "dev", 'cloud_sql_instance' ("zzzz") should have form <project>:<zone>:<instance>`,
},
{
`{"databases":[{"id": "dev", "user": "blah", "db": "blah", "cloud_sql_instance": "a:b:c", "local_socket": "/zzz"}]}`,
`bad config - in "dev", 'local_socket' path "/zzz" must end in "a:b:c"`,
},
}
for idx, c := range cases {
path, cleanup, err := dropFile(c.cfg)
if err != nil {
t.Error(err)
} else {
defer cleanup()
errMsg := ""
if _, err := ReadConfig(path, nil); err != nil {
errMsg = err.Error()
}
if errMsg != c.err {
t.Errorf("for case %d, %q != %q", idx, errMsg, c.err)
}
}
}
}