github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/diff/diff_test.go (about)

     1  package diff
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/authzed/spicedb/pkg/diff/caveats"
    10  	"github.com/authzed/spicedb/pkg/diff/namespace"
    11  	"github.com/authzed/spicedb/pkg/schemadsl/compiler"
    12  	"github.com/authzed/spicedb/pkg/schemadsl/input"
    13  )
    14  
    15  func TestDiffSchemas(t *testing.T) {
    16  	tcs := []struct {
    17  		name             string
    18  		existingSchema   string
    19  		comparisonSchema string
    20  		expectedDiff     SchemaDiff
    21  	}{
    22  		{
    23  			name:             "no changes",
    24  			existingSchema:   `definition user {}`,
    25  			comparisonSchema: `   definition user {}   `,
    26  			expectedDiff:     SchemaDiff{},
    27  		},
    28  		{
    29  			name:             "added namespace",
    30  			existingSchema:   ``,
    31  			comparisonSchema: `definition user {}`,
    32  			expectedDiff: SchemaDiff{
    33  				AddedNamespaces: []string{"user"},
    34  			},
    35  		},
    36  		{
    37  			name:             "removed namespace",
    38  			existingSchema:   `definition user {}`,
    39  			comparisonSchema: ``,
    40  			expectedDiff: SchemaDiff{
    41  				RemovedNamespaces: []string{"user"},
    42  			},
    43  		},
    44  		{
    45  			name:             "added caveat",
    46  			existingSchema:   ``,
    47  			comparisonSchema: `caveat someCaveat(someparam int) { someparam < 42 }`,
    48  			expectedDiff: SchemaDiff{
    49  				AddedCaveats: []string{"someCaveat"},
    50  			},
    51  		},
    52  		{
    53  			name:             "removed caveat",
    54  			existingSchema:   `caveat someCaveat(someparam int) { someparam < 42 }`,
    55  			comparisonSchema: ``,
    56  			expectedDiff: SchemaDiff{
    57  				RemovedCaveats: []string{"someCaveat"},
    58  			},
    59  		},
    60  		{
    61  			name:             "add and remove namespace and caveat",
    62  			existingSchema:   `definition user {}`,
    63  			comparisonSchema: `caveat someCaveat(someparam int) { someparam < 42 }`,
    64  			expectedDiff: SchemaDiff{
    65  				AddedCaveats:      []string{"someCaveat"},
    66  				RemovedNamespaces: []string{"user"},
    67  			},
    68  		},
    69  		{
    70  			name:             "add and remove namespaces",
    71  			existingSchema:   `definition user {}`,
    72  			comparisonSchema: `definition user2 {}`,
    73  			expectedDiff: SchemaDiff{
    74  				AddedNamespaces:   []string{"user2"},
    75  				RemovedNamespaces: []string{"user"},
    76  			},
    77  		},
    78  		{
    79  			name:             "add and remove caveats",
    80  			existingSchema:   `caveat someCaveat(someparam int) { someparam < 42 }`,
    81  			comparisonSchema: `caveat someCaveat2(someparam int) { someparam < 42 }`,
    82  			expectedDiff: SchemaDiff{
    83  				AddedCaveats:   []string{"someCaveat2"},
    84  				RemovedCaveats: []string{"someCaveat"},
    85  			},
    86  		},
    87  	}
    88  
    89  	for _, tc := range tcs {
    90  		t.Run(tc.name, func(t *testing.T) {
    91  			existingSchema, err := compiler.Compile(compiler.InputSchema{
    92  				Source:       input.Source("schema"),
    93  				SchemaString: tc.existingSchema,
    94  			}, compiler.AllowUnprefixedObjectType())
    95  			require.NoError(t, err)
    96  
    97  			comparisonSchema, err := compiler.Compile(compiler.InputSchema{
    98  				Source:       input.Source("schema"),
    99  				SchemaString: tc.comparisonSchema,
   100  			}, compiler.AllowUnprefixedObjectType())
   101  			require.NoError(t, err)
   102  
   103  			diff, err := DiffSchemas(NewDiffableSchemaFromCompiledSchema(existingSchema), NewDiffableSchemaFromCompiledSchema(comparisonSchema))
   104  			require.NoError(t, err)
   105  			require.Equal(t, tc.expectedDiff, *diff)
   106  		})
   107  	}
   108  }
   109  
   110  func TestDiffSchemasWithChangedNamespace(t *testing.T) {
   111  	existingSchema, err := compiler.Compile(compiler.InputSchema{
   112  		Source:       input.Source("schema"),
   113  		SchemaString: `definition user {}`,
   114  	}, compiler.AllowUnprefixedObjectType())
   115  	require.NoError(t, err)
   116  
   117  	comparisonSchema, err := compiler.Compile(compiler.InputSchema{
   118  		Source:       input.Source("schema"),
   119  		SchemaString: `definition user { relation somerel: user; }`,
   120  	}, compiler.AllowUnprefixedObjectType())
   121  	require.NoError(t, err)
   122  
   123  	diff, err := DiffSchemas(NewDiffableSchemaFromCompiledSchema(existingSchema), NewDiffableSchemaFromCompiledSchema(comparisonSchema))
   124  	require.NoError(t, err)
   125  
   126  	require.Len(t, diff.ChangedNamespaces, 1)
   127  	require.Contains(t, diff.ChangedNamespaces, "user")
   128  	require.Len(t, diff.ChangedNamespaces["user"].Deltas(), 1)
   129  	require.Equal(t, namespace.AddedRelation, diff.ChangedNamespaces["user"].Deltas()[0].Type)
   130  	require.Equal(t, "somerel", diff.ChangedNamespaces["user"].Deltas()[0].RelationName)
   131  }
   132  
   133  func TestDiffSchemasWithChangedCaveat(t *testing.T) {
   134  	existingSchema, err := compiler.Compile(compiler.InputSchema{
   135  		Source:       input.Source("schema"),
   136  		SchemaString: `caveat someCaveat(someparam int) { someparam < 42 }`,
   137  	}, compiler.AllowUnprefixedObjectType())
   138  	require.NoError(t, err)
   139  
   140  	comparisonSchema, err := compiler.Compile(compiler.InputSchema{
   141  		Source:       input.Source("schema"),
   142  		SchemaString: `caveat someCaveat(someparam int) { someparam <= 42 }`,
   143  	}, compiler.AllowUnprefixedObjectType())
   144  	require.NoError(t, err)
   145  
   146  	diff, err := DiffSchemas(NewDiffableSchemaFromCompiledSchema(existingSchema), NewDiffableSchemaFromCompiledSchema(comparisonSchema))
   147  	require.NoError(t, err)
   148  
   149  	require.Len(t, diff.ChangedCaveats, 1)
   150  	require.Contains(t, diff.ChangedCaveats, "someCaveat")
   151  	require.Len(t, diff.ChangedCaveats["someCaveat"].Deltas(), 1)
   152  	require.Equal(t, caveats.CaveatExpressionChanged, diff.ChangedCaveats["someCaveat"].Deltas()[0].Type)
   153  }
   154  
   155  func TestDiffSchemasWithChangedCaveatComment(t *testing.T) {
   156  	existingSchema, err := compiler.Compile(compiler.InputSchema{
   157  		Source: input.Source("schema"),
   158  		SchemaString: `// hi there
   159  		caveat someCaveat(someparam int) { someparam < 42 }`,
   160  	}, compiler.AllowUnprefixedObjectType())
   161  	require.NoError(t, err)
   162  
   163  	comparisonSchema, err := compiler.Compile(compiler.InputSchema{
   164  		Source: input.Source("schema"),
   165  		SchemaString: `// hello there
   166  		caveat someCaveat(someparam int) { someparam < 42 }`,
   167  	}, compiler.AllowUnprefixedObjectType())
   168  	require.NoError(t, err)
   169  
   170  	diff, err := DiffSchemas(NewDiffableSchemaFromCompiledSchema(existingSchema), NewDiffableSchemaFromCompiledSchema(comparisonSchema))
   171  	require.NoError(t, err)
   172  
   173  	require.Len(t, diff.ChangedCaveats, 1)
   174  	require.Contains(t, diff.ChangedCaveats, "someCaveat")
   175  	require.Len(t, diff.ChangedCaveats["someCaveat"].Deltas(), 1)
   176  	require.Equal(t, caveats.CaveatCommentsChanged, diff.ChangedCaveats["someCaveat"].Deltas()[0].Type)
   177  }
   178  
   179  type checker func(*testing.T, *DiffableSchema)
   180  
   181  func TestDiffableSchema(t *testing.T) {
   182  	tcs := []struct {
   183  		name     string
   184  		schema   string
   185  		checkers []checker
   186  	}{
   187  		{
   188  			name: "basic schema",
   189  			schema: `
   190  			definition user {}
   191  
   192  			caveat someCaveat(someparam int) { someparam < 42 }
   193  
   194  			definition resource {
   195  				relation owner: user
   196  				relation viewer: user
   197  				permission view = owner + viewer
   198  			}
   199  			`,
   200  			checkers: []checker{
   201  				func(t *testing.T, ds *DiffableSchema) {
   202  					ns, ok := ds.GetNamespace("user")
   203  					require.True(t, ok)
   204  					require.Equal(t, "user", ns.Name)
   205  				},
   206  				func(t *testing.T, ds *DiffableSchema) {
   207  					ns, ok := ds.GetNamespace("resource")
   208  					require.True(t, ok)
   209  					require.Equal(t, "resource", ns.Name)
   210  				},
   211  				func(t *testing.T, ds *DiffableSchema) {
   212  					caveat, ok := ds.GetCaveat("someCaveat")
   213  					require.True(t, ok)
   214  					require.Equal(t, "someCaveat", caveat.Name)
   215  				},
   216  				func(t *testing.T, ds *DiffableSchema) {
   217  					_, ok := ds.GetRelation("user", "owner")
   218  					require.False(t, ok)
   219  				},
   220  				func(t *testing.T, ds *DiffableSchema) {
   221  					_, ok := ds.GetRelation("resource", "owner")
   222  					require.True(t, ok)
   223  				},
   224  				func(t *testing.T, ds *DiffableSchema) {
   225  					_, ok := ds.GetRelation("resource", "viewer")
   226  					require.True(t, ok)
   227  				},
   228  				func(t *testing.T, ds *DiffableSchema) {
   229  					_, ok := ds.GetNamespace("nonexistent")
   230  					require.False(t, ok)
   231  				},
   232  				func(t *testing.T, ds *DiffableSchema) {
   233  					_, ok := ds.GetCaveat("nonexistent")
   234  					require.False(t, ok)
   235  				},
   236  			},
   237  		},
   238  	}
   239  
   240  	for _, tc := range tcs {
   241  		tc := tc
   242  		t.Run(tc.name, func(t *testing.T) {
   243  			schema, err := compiler.Compile(compiler.InputSchema{
   244  				Source:       input.Source("schema"),
   245  				SchemaString: tc.schema,
   246  			}, compiler.AllowUnprefixedObjectType())
   247  			require.NoError(t, err)
   248  
   249  			diffableSchema := NewDiffableSchemaFromCompiledSchema(schema)
   250  			for index, check := range tc.checkers {
   251  				check := check
   252  				t.Run(fmt.Sprintf("check-%d", index), func(t *testing.T) {
   253  					check(t, &diffableSchema)
   254  				})
   255  			}
   256  		})
   257  	}
   258  }