Skip to content

Commit

Permalink
chore: Add unit tests (#24)
Browse files Browse the repository at this point in the history
* chore: Add unit tests
  • Loading branch information
pgollangi committed Nov 17, 2023
1 parent 3c36ef3 commit c782f46
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ jobs:
with:
go-version: '1.19'

- name: 'Set up Cloud SDK'
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: 'google-github-actions/setup-gcloud@v1'
- name: Install Google Cloud SDK components
if: steps.check-for-backend.outputs.has-backend == 'true'
run: yes | gcloud components install beta cloud-firestore-emulator
- name: Test backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ jobs:
echo "has-backend=true" >> $GITHUB_OUTPUT
fi
- name: 'Set up Cloud SDK'
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: 'google-github-actions/setup-gcloud@v1'
- name: Install Google Cloud SDK components
if: steps.check-for-backend.outputs.has-backend == 'true'
run: yes | gcloud components install beta cloud-firestore-emulator
- name: Test backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
cloud.google.com/go/firestore v1.14.0
github.com/grafana/grafana-plugin-sdk-go v0.156.0
github.com/pgollangi/fireql v0.3.2
github.com/stretchr/testify v1.8.2
golang.org/x/oauth2 v0.14.0
google.golang.org/api v0.150.0
)
Expand All @@ -23,6 +24,7 @@ require (
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/getkin/kin-openapi v0.94.0 // indirect
Expand Down Expand Up @@ -60,6 +62,7 @@ require (
github.com/oklog/run v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.40.0 // indirect
Expand All @@ -86,4 +89,5 @@ require (
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
Expand Down
2 changes: 1 addition & 1 deletion pkg/plugin/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRe
defer client.Close()
collections := client.Collections(ctx)
collection, err := collections.Next()
if err == nil || err == iterator.Done {
if err == nil || errors.Is(err, iterator.Done) {
log.DefaultLogger.Debug("First collections: ", collection.ID)
} else {
log.DefaultLogger.Error("client.Collections ", err)
Expand Down
290 changes: 285 additions & 5 deletions pkg/plugin/datasource_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package plugin

import (
"cloud.google.com/go/firestore"
"context"
"encoding/json"
"fmt"
"github.com/stretchr/testify/require"
"io"
"log"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"testing"

"github.com/grafana/grafana-plugin-sdk-go/backend"
Expand All @@ -10,19 +21,288 @@ import (
func TestQueryData(t *testing.T) {
ds := Datasource{}

var settings FirestoreSettings
settings.ProjectId = "test"
jsonSettings, err := json.Marshal(settings)
if err != nil {
t.Error(err)
}

var queries = make([]backend.DataQuery, len(queryTests))
var byRefs = make(map[string]TestExpect, len(queryTests))
for idx, queryTest := range queryTests {
refID := fmt.Sprintf("ref%d", idx)
queries[idx] = backend.DataQuery{
RefID: refID,
JSON: []byte(fmt.Sprintf(`{"query": "%s"}`, queryTest.query)),
}
byRefs[refID] = queryTest
}

resp, err := ds.QueryData(
context.Background(),
&backend.QueryDataRequest{
Queries: []backend.DataQuery{
{RefID: "A"},
PluginContext: backend.PluginContext{
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{
JSONData: jsonSettings,
},
},
Queries: queries,
},
)
require.NoError(t, err)
require.NotNil(t, resp.Responses)
require.Len(t, resp.Responses, len(queryTests))

for refId, response := range resp.Responses {
require.NoError(t, response.Error)
testExp := byRefs[refId]
require.Len(t, response.Frames, 1)
require.Len(t, response.Frames[0].Fields, testExp.columnsLength)
for _, field := range response.Frames[0].Fields {
require.Equal(t, testExp.rowsLength, field.Len())
}
}
}

type healthTest struct {
settings string
decrypted map[string]string
status backend.HealthStatus
}

var healthTests = []healthTest{
{``, nil, backend.HealthStatusError},
{`{}`, nil, backend.HealthStatusError},
{`{"ProjectId": "test"}`, map[string]string{"serviceAccount": "test"}, backend.HealthStatusError},
{`{"ProjectId": "test"}`, map[string]string{"serviceAccount": `{}`}, backend.HealthStatusError},
{`{"ProjectId": "test"}`, nil, backend.HealthStatusOk},
}

func TestCheckHealth(t *testing.T) {
ds := Datasource{}
for _, test := range healthTests {
healthResponse, err := ds.CheckHealth(context.Background(), &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{
JSONData: []byte(test.settings),
DecryptedSecureJSONData: test.decrypted,
},
},
})
require.NoError(t, err)
require.NotNil(t, healthResponse)
require.Equal(t, test.status, healthResponse.Status)
}

}

type TestExpect struct {
query string
rowsLength int
columnsLength int
columns []string
frames [][]interface{}
}

const FirestoreEmulatorHost = "FIRESTORE_EMULATOR_HOST"

var queryTests = []TestExpect{
{
query: "select * from users",
rowsLength: 5,
columnsLength: 6,
},
//{
// query: "select * from `users`",
// columns: []string{"id", "email", "username", "address", "name"},
// length: "21",
//},
//{
// query: "select id as uid, * from users",
// columns: []string{"uid", "id", "email", "username", "address", "name"},
// length: "21",
//},
//{
// query: "select *, username as uname from users",
// columns: []string{"id", "email", "username", "address", "name", "uname"},
// length: "21",
//},
//{
// query: "select id as uid, *, username as uname from users",
// columns: []string{"uid", "id", "email", "username", "address", "name", "uname"},
// length: "21",
//},
//{
// query: "select id, email, address from users",
// columns: []string{"id", "email", "address"},
// length: "21",
//},
//{
// query: "select id, email, address from users limit 5",
// columns: []string{"id", "email", "address"},
// length: "5",
//},
//{
// query: "select id from users where email='aeatockj@psu.edu'",
// columns: []string{"id"},
// length: "1",
// records: [][]interface{}{{float64(20)}},
//},
//{
// query: "select id from users order by id desc limit 1",
// columns: []string{"id"},
// length: "1",
// records: [][]interface{}{{float64(21)}},
//},
//{
// query: "select LENGTH(username) as uLen from users where id = 8",
// columns: []string{"uLen"},
// length: "1",
// records: [][]interface{}{{float64(6)}},
//},
//{
// query: "select id from users where `address.city` = 'Glendale' and name = 'Eleanora'",
// columns: []string{"id"},
// length: "1",
// records: [][]interface{}{{float64(10)}},
//},
//{
// query: "select id > 0 as has_id from users where `address.city` = 'Glendale' and name = 'Eleanora'",
// columns: []string{"has_id"},
// length: "1",
// records: [][]interface{}{{true}},
//},
//{
// query: "select __name__ from users where id = 1",
// columns: []string{"__name__"},
// length: "1",
// records: [][]interface{}{{"1"}},
//},
//{
// query: "select id, email, username from users where id = 21",
// columns: []string{"id", "email", "username"},
// length: "1",
// records: [][]interface{}{{float64(21), nil, "ckensleyk"}},
//},
}

func newFirestoreTestClient(ctx context.Context) *firestore.Client {
client, err := firestore.NewClient(ctx, "test")
if err != nil {
t.Error(err)
log.Fatalf("firebase.NewClient err: %v", err)
}

return client
}

func TestMain(m *testing.M) {
// command to start firestore emulator
cmd := exec.Command("gcloud", "beta", "emulators", "firestore", "start", "--host-port=localhost:8765")

// this makes it killable
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

// we need to capture it's output to know when it's started
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
defer stderr.Close()

if len(resp.Responses) != 1 {
t.Fatal("QueryData must return a response")
// start her up!
if err := cmd.Start(); err != nil {
log.Fatal(err)
}

// ensure the process is killed when we're finished, even if an error occurs
// (thanks to Brian Moran for suggestion)
var result int
defer func() {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
os.Exit(result)
}()

// we're going to wait until it's running to start
var wg sync.WaitGroup
wg.Add(1)

// by starting a separate go routine
go func() {
// reading it's output
buf := make([]byte, 256, 256)
for {
n, err := stderr.Read(buf[:])
if err != nil {
// until it ends
if err == io.EOF {
break
}
log.Fatalf("reading stderr %v", err)
}

if n > 0 {
d := string(buf[:n])

// only required if we want to see the emulator output
log.Printf("%s", d)

// checking for the message that it's started
if strings.Contains(d, "Dev App Server is now running") {
wg.Done()
}

}
}
}()

// wait until the running message has been received
wg.Wait()

os.Setenv(FirestoreEmulatorHost, "localhost:8765")
ctx := context.Background()
users := newFirestoreTestClient(ctx).Collection("users")

usersDataRaw, _ := os.ReadFile("../../test/data/users.json")
var usersData []map[string]interface{}
json.Unmarshal(usersDataRaw, &usersData)

for _, user := range usersData {
users.Doc(fmt.Sprintf("%v", user["id"].(float64))).Set(ctx, user)
}

//selectTests = append(selectTests, TestExpect{query: "select * from users", expected: usersData})
// now it's running, we can run our unit tests
result = m.Run()
}

//func TestSelectQueries(t *testing.T) {
// for _, tt := range selectTests {
// stmt := New(&util.Context{
// ProjectId: "test",
// }, tt.query)
// actual, err := stmt.Execute()
// if err != nil {
// t.Error(err)
// } else {
// less := func(a, b string) bool { return a < b }
// if cmp.Diff(tt.columns, actual.Columns, cmpopts.SortSlices(less)) != "" {
// t.Errorf("QueryResult.Fields(%v): expected %v, actual %v", tt.query, tt.columns, actual.Columns)
// }
// if tt.length != "" && len(actual.Records) != first(strconv.Atoi(tt.length)) {
// t.Errorf("len(QueryResult.Records)(%v): expected %v, actual %v", tt.query, len(actual.Records), tt.length)
// }
// if tt.records != nil && !cmp.Equal(actual.Records, tt.records) {
// a, _ := json.Marshal(tt.records)
// log.Println(string(a))
// a, _ = json.Marshal(actual.Records)
// log.Println(string(a))
// t.Errorf("QueryResult.Records(%v): expected %v, actual %v", tt.query, tt.records, actual.Records)
// }
// }
// }
//}
//
//func first(n int, _ error) int {
// return n
//}
Loading

0 comments on commit c782f46

Please sign in to comment.