Skip to content

Commit

Permalink
Test added for verification api
Browse files Browse the repository at this point in the history
  • Loading branch information
Vivek Yadav authored and taherkl committed Dec 20, 2024
1 parent a226a70 commit 4bba7a5
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 54 deletions.
10 changes: 10 additions & 0 deletions sources/common/toddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ func (ss *SchemaToSpannerImpl) VerifyExpressions(conv *internal.Conv) {
return
}

if conv.SchemaIssues == nil {
conv.SchemaIssues = make(map[string]internal.TableIssues)
}

if _, exists := conv.SchemaIssues[tableId]; !exists {
conv.SchemaIssues[tableId] = internal.TableIssues{
ColumnLevelIssues: make(map[string][]internal.SchemaIssue),
}
}

colIssue := conv.SchemaIssues[tableId].ColumnLevelIssues[colId]

if !IsSchemaIssuePresent(colIssue, issueType) {
Expand Down
104 changes: 104 additions & 0 deletions sources/common/toddl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
package common

import (
"context"
"errors"
"reflect"
"testing"

"github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants"
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
"github.com/GoogleCloudPlatform/spanner-migration-tool/mocks"
"github.com/GoogleCloudPlatform/spanner-migration-tool/schema"
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func Test_quoteIfNeeded(t *testing.T) {
Expand Down Expand Up @@ -469,3 +473,103 @@ func Test_cvtCheckContraint(t *testing.T) {
result := cvtCheckConstraint(conv, srcSchema)
assert.Equal(t, spSchema, result)
}

func TestVerifyCheckConstraintExpressions(t *testing.T) {
tests := []struct {
name string
expressions []ddl.CheckConstraint
expectedResults []internal.ExpressionVerificationOutput
expectedCheckConstraint []ddl.CheckConstraint
expectedResponse bool
}{
{
name: "AllValidExpressions",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
},
expectedCheckConstraint: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResponse: false,
},
{
name: "InvalidSyntaxError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("Syntax error ..."), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedCheckConstraint: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResponse: true,
},
{
name: "NameError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18)", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("Unrecognized name ..."), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedCheckConstraint: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResponse: true,
},
{
name: "TypeError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18)", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("No matching signature for operator"), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedCheckConstraint: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResponse: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockAccessor := new(mocks.MockExpressionVerificationAccessor)
handler := &SchemaToSpannerImpl{ExpressionVerificationAccessor: mockAccessor}

conv := internal.MakeConv()

ctx := context.Background()

conv.SpSchema = map[string]ddl.CreateTable{
"t1": {
Name: "table1",
Id: "t1",
PrimaryKeys: []ddl.IndexKey{{ColId: "c1"}},
ColIds: []string{"c1"},
ColDefs: map[string]ddl.ColumnDef{
"c1": {Name: "col1", Id: "c1", T: ddl.Type{Name: ddl.Int64}},
},
CheckConstraints: tc.expressions,
},
}

mockAccessor.On("VerifyExpressions", ctx, mock.Anything).Return(internal.VerifyExpressionsOutput{
ExpressionVerificationOutputList: tc.expectedResults,
})
handler.VerifyExpressions(conv)
assert.Equal(t, conv.SpSchema["t1"].CheckConstraints, tc.expectedCheckConstraint)

})
}
}
10 changes: 10 additions & 0 deletions webv2/api/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,16 @@ func (expressionVerificationHandler *ExpressionsVerificationHandler) VerifyCheck
return
}

if sessionState.Conv.SchemaIssues == nil {
sessionState.Conv.SchemaIssues = make(map[string]internal.TableIssues)
}

if _, exists := sessionState.Conv.SchemaIssues[tableId]; !exists {
sessionState.Conv.SchemaIssues[tableId] = internal.TableIssues{
ColumnLevelIssues: make(map[string][]internal.SchemaIssue),
}
}

colIssue := sessionState.Conv.SchemaIssues[tableId].ColumnLevelIssues[colId]

if !utilities.IsSchemaIssuePresent(colIssue, issueType) {
Expand Down
148 changes: 94 additions & 54 deletions webv2/api/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -16,6 +16,7 @@ import (
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal/reports"
"github.com/GoogleCloudPlatform/spanner-migration-tool/logger"
"github.com/GoogleCloudPlatform/spanner-migration-tool/mocks"
"github.com/GoogleCloudPlatform/spanner-migration-tool/proto/migration"
"github.com/GoogleCloudPlatform/spanner-migration-tool/schema"
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
Expand Down Expand Up @@ -2640,66 +2641,105 @@ func (m *MockExpressionVerificationAccessor) VerifyExpressions(ctx context.Conte
return args.Get(0).(internal.VerifyExpressionsOutput)
}

func TestVerifyCheckConstraintExpression(t *testing.T) {
// Arrange
mockAccessor := new(MockExpressionVerificationAccessor)
handler := &api.ExpressionsVerificationHandler{ExpressionVerificationAccessor: mockAccessor}

req, err := http.NewRequest("POST", "/checkConstraint", nil) // Set nil as we'll overwrite it
if err != nil {
t.Fatal(err)
}

// Simulate context and setup session
ctx := req.Context()
sessionState := session.GetSessionState()
sessionState.Driver = constants.MYSQL
sessionState.SpannerInstanceID = "foo"
sessionState.SpannerProjectId = "daring-12"
sessionState.Conv = internal.MakeConv()
sessionState.Conv.SpSchema = map[string]ddl.CreateTable{
"t1": {
Name: "table1",
Id: "t1",
PrimaryKeys: []ddl.IndexKey{{ColId: "c1"}},
CheckConstraints: []ddl.CheckConstraint{
{Expr: "col1 > 0", ExprId: "expr1", Name: "check1"},
func TestVerifyCheckConstraintExpressions(t *testing.T) {
tests := []struct {
name string
expressions []ddl.CheckConstraint
expectedResults []internal.ExpressionVerificationOutput
expectedResponse bool
}{
{
name: "AllValidExpressions",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
},
expectedResponse: false,
},
}

// Setup request body
expressionDetails := []internal.ExpressionDetail{
// Add expression details to verify
}
body, _ := json.Marshal(expressionDetails)
req.Body = ioutil.NopCloser(bytes.NewReader(body))

// Mock VerifyExpressions
mockAccessor.On("VerifyExpressions", ctx, mock.Anything).Return(internal.VerifyExpressionsOutput{
ExpressionVerificationOutputList: []internal.ExpressionVerificationOutput{
{
Result: true,
{
name: "InvalidSyntaxError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("Syntax error ..."), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedResponse: true,
},
})
{
name: "NameError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18)", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("Unrecognized name ..."), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedResponse: true,
},
{
name: "TypeError",
expressions: []ddl.CheckConstraint{
{Expr: "(col1 > 0)", ExprId: "expr1", Name: "check1"},
{Expr: "(col1 > 18)", ExprId: "expr2", Name: "check2"},
},
expectedResults: []internal.ExpressionVerificationOutput{
{Result: true, Err: nil, ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 0)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check1"}, ExpressionId: "expr1"}},
{Result: false, Err: errors.New("No matching signature for operator"), ExpressionDetail: internal.ExpressionDetail{Expression: "(col1 > 18)", Type: "CHECK", Metadata: map[string]string{"tableId": "t1", "colId": "c1", "checkConstraintName": "check2"}, ExpressionId: "expr2"}},
},
expectedResponse: true,
},
}

// Use httptest to record the response
rr := httptest.NewRecorder()
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mockAccessor := new(mocks.MockExpressionVerificationAccessor)
handler := &api.ExpressionsVerificationHandler{ExpressionVerificationAccessor: mockAccessor}

req, err := http.NewRequest("POST", "/verifyCheckConstraintExpression", nil)
if err != nil {
t.Fatal(err)
}

ctx := req.Context()
sessionState := session.GetSessionState()
sessionState.Driver = constants.MYSQL
sessionState.SpannerInstanceID = "foo"
sessionState.SpannerProjectId = "daring-12"
sessionState.Conv = internal.MakeConv()
sessionState.Conv.SpSchema = map[string]ddl.CreateTable{
"t1": {
Name: "table1",
Id: "t1",
PrimaryKeys: []ddl.IndexKey{{ColId: "c1"}},
ColIds: []string{"c1"},
ColDefs: map[string]ddl.ColumnDef{
"c1": {Name: "col1", Id: "c1", T: ddl.Type{Name: ddl.Int64}},
},
CheckConstraints: tc.expressions,
},
}

// Act
handler.VerifyCheckConstraintExpression(rr, req)
mockAccessor.On("VerifyExpressions", ctx, mock.Anything).Return(internal.VerifyExpressionsOutput{
ExpressionVerificationOutputList: tc.expectedResults,
})

// Assert
assert.Equal(t, http.StatusOK, rr.Code)
rr := httptest.NewRecorder()
handler.VerifyCheckConstraintExpression(rr, req)

var response bool
err = json.NewDecoder(rr.Body).Decode(&response)
if err != nil {
t.Fatal(err)
}
assert.False(t, response) // No errors should have occurred if no invalid expressions
assert.Equal(t, http.StatusOK, rr.Code)

// Verify if the VerifyExpressions was called
mockAccessor.AssertNumberOfCalls(t, "VerifyExpressions", 1)
var response bool
err = json.NewDecoder(rr.Body).Decode(&response)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, tc.expectedResponse, response)
})
}
}

0 comments on commit 4bba7a5

Please sign in to comment.