github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/appdefcompat/impl_test.go (about)

     1  /*
     2  * Copyright (c) 2023-present unTill Pro, Ltd.
     3  * @author Alisher Nurmanov
     4   */
     5  package appdefcompat
     6  
     7  import (
     8  	"embed"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/require"
    14  	"golang.org/x/exp/slices"
    15  
    16  	"github.com/voedger/voedger/pkg/appdef"
    17  	"github.com/voedger/voedger/pkg/parser"
    18  )
    19  
    20  //go:embed sql/sys.old.vsql
    21  var oldSysSchemaFS embed.FS
    22  
    23  //go:embed sql/pkg1.old.vsql
    24  var oldPkg1SchemaFS embed.FS
    25  
    26  //go:embed sql/pkg2.vsql
    27  var pkg2SchemaFS embed.FS
    28  
    29  //go:embed sql/sys.new.vsql
    30  var newSysSchemaFS embed.FS
    31  
    32  //go:embed sql/pkg1.new.vsql
    33  var newPkg1SchemaFS embed.FS
    34  
    35  func Test_Basic(t *testing.T) {
    36  	oldSysPkgAST, err := parser.ParsePackageDir(appdef.SysPackage, oldSysSchemaFS, "sql")
    37  	require.NoError(t, err)
    38  
    39  	oldPkg1AST, err := parser.ParsePackageDir("pkg1", oldPkg1SchemaFS, "sql")
    40  	require.NoError(t, err)
    41  
    42  	oldpkg2AST, err := parser.ParsePackageDir("pkg2", pkg2SchemaFS, "sql")
    43  	require.NoError(t, err)
    44  
    45  	newpkg2AST, err := parser.ParsePackageDir("pkg2", pkg2SchemaFS, "sql")
    46  	require.NoError(t, err)
    47  
    48  	oldPackages, err := parser.BuildAppSchema([]*parser.PackageSchemaAST{oldSysPkgAST, oldPkg1AST, oldpkg2AST})
    49  	require.NoError(t, err)
    50  
    51  	newSysPkgAST, err := parser.ParsePackageDir(appdef.SysPackage, newSysSchemaFS, "sql")
    52  	require.NoError(t, err)
    53  
    54  	newPkg1AST, err := parser.ParsePackageDir("pkg1", newPkg1SchemaFS, "sql")
    55  	require.NoError(t, err)
    56  
    57  	newPackages, err := parser.BuildAppSchema([]*parser.PackageSchemaAST{newSysPkgAST, newPkg1AST, newpkg2AST})
    58  	require.NoError(t, err)
    59  
    60  	oldBuilder := appdef.New()
    61  	require.NoError(t, parser.BuildAppDefs(oldPackages, oldBuilder))
    62  
    63  	newBuilder := appdef.New()
    64  	require.NoError(t, parser.BuildAppDefs(newPackages, newBuilder))
    65  
    66  	oldAppDef, err := oldBuilder.Build()
    67  	require.NoError(t, err)
    68  
    69  	newAppDef, err := newBuilder.Build()
    70  	require.NoError(t, err)
    71  
    72  	t.Run("CheckBackwardCompatibility", func(t *testing.T) {
    73  		expectedErrors := []CompatibilityError{
    74  			{OldTreePath: []string{"AppDef", "Types", "sys.Profile", "Types", "sys.ProfileTable"}, ErrorType: ErrorTypeNodeRemoved},
    75  			{OldTreePath: []string{"AppDef", "Types", "sys.ProfileTable"}, ErrorType: ErrorTypeNodeRemoved},
    76  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeTable"}, ErrorType: ErrorTypeNodeRemoved},
    77  			{OldTreePath: []string{"AppDef", "Types", "sys.CreateLoginUnloggedParams", "Fields", "Password"}, ErrorType: ErrorTypeOrderChanged},
    78  			{OldTreePath: []string{"AppDef", "Types", "sys.CreateLoginUnloggedParams", "Fields", "Email"}, ErrorType: ErrorTypeOrderChanged},
    79  			{OldTreePath: []string{"AppDef", "Types", "sys.CreateLoginParams", "Fields", "Login"}, ErrorType: ErrorTypeNodeRemoved},
    80  			{OldTreePath: []string{"AppDef", "Types", "sys.CreateLoginParams", "Fields", "ProfileCluster"}, ErrorType: ErrorTypeValueChanged},
    81  			{OldTreePath: []string{"AppDef", "Types", "sys.CreateLoginParams", "Fields", "ProfileToken"}, ErrorType: ErrorTypeValueChanged},
    82  			{OldTreePath: []string{"AppDef", "Types", "sys.AnotherOneTable", "Fields"}, ErrorType: ErrorTypeNodeInserted},
    83  			{OldTreePath: []string{"AppDef", "Types", "sys.AnotherOneTable", "Fields", "C"}, ErrorType: ErrorTypeValueChanged},
    84  			{OldTreePath: []string{"AppDef", "Types", "sys.AnotherOneTable", "Fields", "C"}, ErrorType: ErrorTypeOrderChanged},
    85  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeWorkspace", "Types", "sys.SomeTable"}, ErrorType: ErrorTypeNodeRemoved},
    86  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeCommand", "CommandArgs"}, ErrorType: ErrorTypeValueChanged},
    87  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeCommand", "UnloggedArgs"}, ErrorType: ErrorTypeValueChanged},
    88  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeCommand", "CommandResult"}, ErrorType: ErrorTypeValueChanged},
    89  			{OldTreePath: []string{"AppDef", "Types", "sys.Workspace", "Abstract"}, ErrorType: ErrorTypeValueChanged},
    90  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeView", "PartKeyFields"}, ErrorType: ErrorTypeNodeModified},
    91  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeView", "Fields", "E"}, ErrorType: ErrorTypeValueChanged},
    92  			{OldTreePath: []string{"AppDef", "Types", "sys.SomeView", "ClustColsFields", "B"}, ErrorType: ErrorTypeValueChanged},
    93  			{OldTreePath: []string{"AppDef", "Types", "sys.AnotherOneTable", "Uniques", "sys.AnotherOneTable$uniques$01", "UniqueFields"}, ErrorType: ErrorTypeNodeModified},
    94  			{OldTreePath: []string{"AppDef", "Packages", "pkg1"}, ErrorType: ErrorTypeValueChanged},
    95  			{OldTreePath: []string{"AppDef", "Packages", "pkg2"}, ErrorType: ErrorTypeValueChanged},
    96  		}
    97  		compatErrors := CheckBackwardCompatibility(oldAppDef, newAppDef)
    98  		fmt.Println(compatErrors.Error())
    99  		validateCompatibilityErrors2(t, expectedErrors, compatErrors)
   100  	})
   101  
   102  	t.Run("IgnoreCompatibilityErrors", func(t *testing.T) {
   103  		// testing ignoring some compatibility errors
   104  		pathsToIgnore := [][]string{
   105  			{"AppDef", "Types", "sys.CreateLoginParams", "Fields", "ProfileToken"},
   106  		}
   107  		compatErrors := CheckBackwardCompatibility(oldAppDef, newAppDef)
   108  		fmt.Println(compatErrors.Error())
   109  		filteredCompatErrors := IgnoreCompatibilityErrors(compatErrors, pathsToIgnore)
   110  		checkPathsToIgnore(t, pathsToIgnore, compatErrors, filteredCompatErrors)
   111  	})
   112  }
   113  
   114  func validateCompatibilityErrors2(t *testing.T, expectedErrors []CompatibilityError, actualErrors *CompatibilityErrors) {
   115  	for _, actualErr := range actualErrors.Errors {
   116  		require.True(t, slices.ContainsFunc(expectedErrors, func(expectedErr CompatibilityError) bool {
   117  			if expectedErr.Path() == actualErr.Path() && expectedErr.ErrorType == actualErr.ErrorType {
   118  				return true
   119  			}
   120  			return false
   121  		}), actualErr.Error())
   122  	}
   123  	for _, expectedErr := range expectedErrors {
   124  		require.True(t, slices.ContainsFunc(actualErrors.Errors, func(actualErr CompatibilityError) bool {
   125  			if expectedErr.Path() == actualErr.Path() && expectedErr.ErrorType == actualErr.ErrorType {
   126  				return true
   127  			}
   128  			return false
   129  		}), "error is expected but not occurred: "+expectedErr.Error())
   130  	}
   131  }
   132  
   133  func checkPathsToIgnore(t *testing.T, pathsToIgnore [][]string, compatErrors, filteredCompatErrors *CompatibilityErrors) {
   134  	for _, pathToIgnore := range pathsToIgnore {
   135  		found := false
   136  		for _, cerr := range compatErrors.Errors {
   137  			if cerr.Path() == strings.Join(pathToIgnore, pathDelimiter) {
   138  				found = true
   139  				break
   140  			}
   141  		}
   142  		require.True(t, found, fmt.Sprintf("there is no path %s in compat errors", pathToIgnore))
   143  	}
   144  	for _, pathToIgnore := range pathsToIgnore {
   145  		found := false
   146  		for _, cerr := range filteredCompatErrors.Errors {
   147  			if cerr.Path() == strings.Join(pathToIgnore, pathDelimiter) {
   148  				found = true
   149  				break
   150  			}
   151  		}
   152  		require.False(t, found, fmt.Sprintf("path %s should be ignored", pathToIgnore))
   153  	}
   154  }