Skip to content

Commit

Permalink
test: create unit test for converter
Browse files Browse the repository at this point in the history
  • Loading branch information
Muhammad Luthfi Fahlevi committed Aug 12, 2024
1 parent a926792 commit 57897ea
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 10 deletions.
98 changes: 98 additions & 0 deletions pkg/query_expr/es_expr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package queryexpr_test

import (
"testing"

queryexpr "github.com/goto/compass/pkg/query_expr"
)

func TestESExpr_String(t *testing.T) {
tests := []struct {
expr queryexpr.SQLExpr
want string
}{
{
expr: queryexpr.SQLExpr("test"),
want: "test",
},
{
expr: queryexpr.SQLExpr("bool_identifier == !(findLast([1, 2, 3, 4], # > 2) == 4)"),
want: "bool_identifier == !(findLast([1, 2, 3, 4], # > 2) == 4)",
},
}
for i, tt := range tests {
t.Run("test-case-"+string(rune(i)), func(t *testing.T) {
if got := tt.expr.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}

func TestESExpr_ToQuery(t *testing.T) {
tests := []struct {
name string
expr queryexpr.ESExpr
want string
wantErr bool
}{
{
name: "less than condition with single quote",
expr: queryexpr.ESExpr(`updated_at < '2024-04-05 23:59:59'`),
want: `{"query":{"range":{"updated_at":{"lt":"2024-04-05 23:59:59"}}}}`,
wantErr: false,
},
{
name: "greater than condition with double quote",
expr: queryexpr.ESExpr(`updated_at > "2024-04-05 23:59:59"`),
want: `{"query":{"range":{"updated_at":{"gt":"2024-04-05 23:59:59"}}}}`,
wantErr: false,
},
{
name: "in condition",
expr: queryexpr.ESExpr(`service in ["test1","test2","test3"]`),
want: `{"query":{"terms":{"service":["test1","test2","test3"]}}}`,
wantErr: false,
},
{
name: "equals or not in condition",
expr: queryexpr.ESExpr(`name == "John" || service not in ["test1","test2","test3"]`),
want: `{"query":{"bool":{"should":[{"term":{"name":"John"}},{"bool":{"must_not":[{"terms":{"service":["test1","test2","test3"]}}]}}]}}}`,
wantErr: false,
},
{
name: "complex query expression that can directly produce a value",
expr: queryexpr.ESExpr(`bool_identifier == !(findLast([1, 2, 3, 4], # > 2) == 4)`),
want: `{"query":{"term":{"bool_identifier":false}}}`,
wantErr: false,
},
{
name: "complex query expression that can NOT directly produce a value",
expr: queryexpr.ESExpr(`service in filter(assets, .Service startsWith "T")`),
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.expr.ToQuery()
if (err != nil) != tt.wantErr {
t.Errorf("ToQuery() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ToQuery() got = %v, want %v", got, tt.want)
}
})
}
}

func TestESExpr_Validate(t *testing.T) {
t.Run("should return nil as default validation", func(t *testing.T) {
expr := queryexpr.ESExpr("query_es == 'test'")
if err := (&expr).Validate(); err != nil {
t.Errorf("Validate() error = %v, wantErr %v", err, nil)
}
return

Check failure on line 96 in pkg/query_expr/es_expr_test.go

View workflow job for this annotation

GitHub Actions / golangci

S1023: redundant `return` statement (gosimple)
})
}
134 changes: 134 additions & 0 deletions pkg/query_expr/query_expr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package queryexpr

Check failure on line 1 in pkg/query_expr/query_expr_test.go

View workflow job for this annotation

GitHub Actions / golangci

package should be `queryexpr_test` instead of `queryexpr` (testpackage)

import (
"reflect"
"testing"
)

func TestGetIdentifiers(t *testing.T) {
tests := []struct {
name string
expr string
want []string
wantErr bool
}{
{
name: "got 0 identifier test",
expr: `findLast([1, 2, 3, 4], # > 2)`,
want: nil,
wantErr: false,
},
{
name: "got 1 identifiers test",
expr: `updated_at < '2024-04-05 23:59:59'`,
want: []string{"updated_at"},
wantErr: false,
},
{
name: "got 3 identifiers test",
expr: `(identifier1 == !(findLast([1, 2, 3, 4], # > 2) == 4)) && identifier2 != 'John' || identifier3 == "hallo"`,
want: []string{"identifier1", "identifier2", "identifier3"},
wantErr: false,
},
{
name: "got error",
expr: `findLast([1, 2, 3, 4], # > 2`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetIdentifiers(tt.expr)
if (err != nil) != tt.wantErr {
t.Errorf("GetIdentifiers() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetIdentifiers() got = %v, want %v", got, tt.want)
}
})
}
}

func TestGetQueryExprResult(t *testing.T) {
tests := []struct {
name string
expr string
want any
wantErr bool
}{
{
name: "return a value from func",
expr: `findLast([1, 2, 3, 4], # > 2)`,
want: 4,
wantErr: false,
},
{
name: "return a value func equation",
expr: `false == !true`,
want: true,
wantErr: false,
},
{
name: "got error due to can NOT directly produce a value",
expr: `updated_at < '2024-04-05 23:59:59'`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetQueryExprResult(tt.expr)
if (err != nil) != tt.wantErr {
t.Errorf("GetQueryExprResult() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetQueryExprResult() got = %v, want %v", got, tt.want)
}
})
}
}

func TestGetTreeNodeFromQueryExpr(t *testing.T) {
tests := []struct {
name string
expr string
want any
wantErr bool
}{
{
name: "success using func from the library",
expr: `findLast([1], # >= 1)`,
want: "findLast([1], # >= 1)",
wantErr: false,
},
{
name: "success using equation",
expr: `false == !true`,
want: "false == !true",
wantErr: false,
},
{
name: "got error using wrong syntax",
expr: `findLast(`,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetTreeNodeFromQueryExpr(tt.expr)
if (err != nil) != tt.wantErr {
t.Errorf("GetTreeNodeFromQueryExpr() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if got.String() != tt.want {
t.Errorf("GetTreeNodeFromQueryExpr() got = %v, want %v", got, tt.want)
}
}
})
}
}
23 changes: 13 additions & 10 deletions pkg/query_expr/sql_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (s *SQLExpr) ToQuery() (string, error) {
return "", err
}
stringBuilder := &strings.Builder{}
if err := s.ConvertToSQL(queryExprParsed, stringBuilder); err != nil {
if err := s.convertToSQL(queryExprParsed, stringBuilder); err != nil {
return "", err
}
return stringBuilder.String(), nil
Expand All @@ -32,9 +32,9 @@ func (*SQLExpr) Validate() error {
return nil
}

// ConvertToSQL The idea came from ast.Walk. Currently, the development focus implement for the node type that most likely used in our needs.
// convertToSQL The idea came from ast.Walk. Currently, the development focus implement for the node type that most likely used in our needs.
// TODO: implement translator for node type that still not covered right now.
func (s *SQLExpr) ConvertToSQL(node ast.Node, stringBuilder *strings.Builder) error {
func (s *SQLExpr) convertToSQL(node ast.Node, stringBuilder *strings.Builder) error {
if node == nil {
return fmt.Errorf("cannot convert nil to SQL query")
}
Expand Down Expand Up @@ -62,7 +62,7 @@ func (s *SQLExpr) ConvertToSQL(node ast.Node, stringBuilder *strings.Builder) er
if err := s.patchUnaryNode(n); err != nil {
return err
}
if err := s.ConvertToSQL(n.Node, stringBuilder); err != nil {
if err := s.convertToSQL(n.Node, stringBuilder); err != nil {
return err
}
case *ast.ArrayNode:
Expand Down Expand Up @@ -93,14 +93,14 @@ func (s *SQLExpr) binaryNodeToSQLQuery(n *ast.BinaryNode, stringBuilder *strings
fmt.Fprintf(stringBuilder, "%v", result)
} else {
stringBuilder.WriteString("(")
if err := s.ConvertToSQL(n.Left, stringBuilder); err != nil {
if err := s.convertToSQL(n.Left, stringBuilder); err != nil {
return err
}

// write operator
fmt.Fprintf(stringBuilder, " %s ", strings.ToUpper(operator))

if err := s.ConvertToSQL(n.Right, stringBuilder); err != nil {
if err := s.convertToSQL(n.Right, stringBuilder); err != nil {
return err
}
stringBuilder.WriteString(")")
Expand All @@ -112,7 +112,7 @@ func (s *SQLExpr) binaryNodeToSQLQuery(n *ast.BinaryNode, stringBuilder *strings
func (s *SQLExpr) arrayNodeToSQLQuery(n *ast.ArrayNode, stringBuilder *strings.Builder) error {
stringBuilder.WriteString("(")
for i := range n.Nodes {
if err := s.ConvertToSQL(n.Nodes[i], stringBuilder); err != nil {
if err := s.convertToSQL(n.Nodes[i], stringBuilder); err != nil {
return err
}
if i != len(n.Nodes)-1 {
Expand All @@ -127,7 +127,7 @@ func (s *SQLExpr) patchUnaryNode(n *ast.UnaryNode) error {
switch n.Operator {
case "not":
binaryNode, ok := (n.Node).(*ast.BinaryNode)
if ok && binaryNode.Operator == "in" {
if ok && strings.ToUpper(binaryNode.Operator) == "IN" {

Check failure on line 130 in pkg/query_expr/sql_expr.go

View workflow job for this annotation

GitHub Actions / golangci

early-return: if c { ... } else { ... return } can be simplified to if !c { ... return } ... (revive)
ast.Patch(&n.Node, &ast.BinaryNode{
Operator: "not in",
Left: binaryNode.Left,
Expand All @@ -149,7 +149,7 @@ func (s *SQLExpr) patchUnaryNode(n *ast.UnaryNode) error {
}
if boolResult, ok := result.(bool); ok {
ast.Patch(&n.Node, &ast.BoolNode{
Value: !boolResult,
Value: boolResult,
})
return nil
}
Expand All @@ -161,7 +161,7 @@ func (s *SQLExpr) patchUnaryNode(n *ast.UnaryNode) error {
}

func (*SQLExpr) operatorToSQL(bn *ast.BinaryNode) string {
switch bn.Operator {
switch strings.ToUpper(bn.Operator) {
case "&&":
return "AND"
case "||":
Expand All @@ -170,13 +170,16 @@ func (*SQLExpr) operatorToSQL(bn *ast.BinaryNode) string {
if _, ok := bn.Right.(*ast.NilNode); ok {
return "IS NOT"
}
return bn.Operator
case "==":
if _, ok := bn.Right.(*ast.NilNode); ok {
return "IS"
}
return "="
case "<", "<=", ">", ">=":
return bn.Operator
case "IN", "NOT IN":
return bn.Operator
}

return "" // identify operation, like: +, -, *, etc
Expand Down
Loading

0 comments on commit 57897ea

Please sign in to comment.