github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/schemadsl/generator/generator_test.go (about)

     1  package generator
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    10  
    11  	"github.com/authzed/spicedb/pkg/caveats"
    12  	caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
    13  	"github.com/authzed/spicedb/pkg/namespace"
    14  	"github.com/authzed/spicedb/pkg/schemadsl/compiler"
    15  	"github.com/authzed/spicedb/pkg/schemadsl/input"
    16  )
    17  
    18  func TestGenerateCaveat(t *testing.T) {
    19  	type generatorTest struct {
    20  		name     string
    21  		input    *core.CaveatDefinition
    22  		expected string
    23  		okay     bool
    24  	}
    25  
    26  	tests := []generatorTest{
    27  		{
    28  			"basic",
    29  			namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
    30  				map[string]caveattypes.VariableType{
    31  					"someParam": caveattypes.IntType,
    32  				},
    33  			), "somecaveat", "someParam == 42"),
    34  			`
    35  caveat somecaveat(someParam int) {
    36  	someParam == 42
    37  }`,
    38  			true,
    39  		},
    40  		{
    41  			"multiparameter",
    42  			namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
    43  				map[string]caveattypes.VariableType{
    44  					"someParam":    caveattypes.IntType,
    45  					"anotherParam": caveattypes.MustMapType(caveattypes.UIntType),
    46  				},
    47  			), "somecaveat", "someParam == 42"),
    48  			`
    49  caveat somecaveat(anotherParam map<uint>, someParam int) {
    50  	someParam == 42
    51  }`,
    52  			true,
    53  		},
    54  		{
    55  			"long",
    56  			namespace.MustCaveatDefinition(caveats.MustEnvForVariables(
    57  				map[string]caveattypes.VariableType{
    58  					"someParam": caveattypes.IntType,
    59  				},
    60  			), "somecaveat", "someParam == 42 && someParam == 43 && someParam == 44 && someParam == 45"),
    61  			`
    62  caveat somecaveat(someParam int) {
    63  	someParam == 42 && someParam == 43 && someParam == 44 && someParam == 45
    64  }`,
    65  			true,
    66  		},
    67  	}
    68  
    69  	for _, test := range tests {
    70  		test := test
    71  		t.Run(test.name, func(t *testing.T) {
    72  			require := require.New(t)
    73  			source, ok, err := GenerateCaveatSource(test.input)
    74  			require.NoError(err)
    75  			require.Equal(strings.TrimSpace(test.expected), source)
    76  			require.Equal(test.okay, ok)
    77  		})
    78  	}
    79  }
    80  
    81  func TestGenerateNamespace(t *testing.T) {
    82  	type generatorTest struct {
    83  		name     string
    84  		input    *core.NamespaceDefinition
    85  		expected string
    86  		okay     bool
    87  	}
    88  
    89  	tests := []generatorTest{
    90  		{
    91  			"empty",
    92  			namespace.Namespace("foos/test"),
    93  			"definition foos/test {}",
    94  			true,
    95  		},
    96  		{
    97  			"simple relation",
    98  			namespace.Namespace("foos/test",
    99  				namespace.MustRelation("somerel", nil, namespace.AllowedRelation("foos/bars", "hiya")),
   100  			),
   101  			`definition foos/test {
   102  	relation somerel: foos/bars#hiya
   103  }`,
   104  			true,
   105  		},
   106  		{
   107  			"simple permission",
   108  			namespace.Namespace("foos/test",
   109  				namespace.MustRelation("someperm", namespace.Union(
   110  					namespace.ComputedUserset("anotherrel"),
   111  				)),
   112  			),
   113  			`definition foos/test {
   114  	permission someperm = anotherrel
   115  }`,
   116  			true,
   117  		},
   118  		{
   119  			"complex permission",
   120  			namespace.Namespace("foos/test",
   121  				namespace.MustRelation("someperm", namespace.Union(
   122  					namespace.Rewrite(
   123  						namespace.Exclusion(
   124  							namespace.ComputedUserset("rela"),
   125  							namespace.ComputedUserset("relb"),
   126  							namespace.TupleToUserset("rely", "relz"),
   127  						),
   128  					),
   129  					namespace.ComputedUserset("relc"),
   130  				)),
   131  			),
   132  			`definition foos/test {
   133  	permission someperm = (rela - relb - rely->relz) + relc
   134  }`,
   135  			true,
   136  		},
   137  		{
   138  			"complex permission with nil",
   139  			namespace.Namespace("foos/test",
   140  				namespace.MustRelation("someperm", namespace.Union(
   141  					namespace.Rewrite(
   142  						namespace.Exclusion(
   143  							namespace.ComputedUserset("rela"),
   144  							namespace.ComputedUserset("relb"),
   145  							namespace.TupleToUserset("rely", "relz"),
   146  							namespace.Nil(),
   147  						),
   148  					),
   149  					namespace.ComputedUserset("relc"),
   150  				)),
   151  			),
   152  			`definition foos/test {
   153  	permission someperm = (rela - relb - rely->relz - nil) + relc
   154  }`,
   155  			true,
   156  		},
   157  		{
   158  			"legacy relation",
   159  			namespace.Namespace("foos/test",
   160  				namespace.MustRelation("somerel", namespace.Union(
   161  					&core.SetOperation_Child{
   162  						ChildType: &core.SetOperation_Child_XThis{},
   163  					},
   164  					namespace.ComputedUserset("anotherrel"),
   165  				), namespace.AllowedRelation("foos/bars", "hiya")),
   166  			),
   167  			`definition foos/test {
   168  	relation somerel: foos/bars#hiya = /* _this unsupported here. Please rewrite into a relation and permission */ + anotherrel
   169  }`,
   170  			false,
   171  		},
   172  		{
   173  			"missing type information",
   174  			namespace.Namespace("foos/test",
   175  				namespace.MustRelation("somerel", nil),
   176  			),
   177  			`definition foos/test {
   178  	relation somerel: /* missing allowed types */
   179  }`,
   180  			false,
   181  		},
   182  
   183  		{
   184  			"full example",
   185  			namespace.WithComment("foos/document", `/**
   186  * Some comment goes here
   187  */`,
   188  				namespace.MustRelation("owner", nil,
   189  					namespace.AllowedRelation("foos/user", "..."),
   190  				),
   191  				namespace.MustRelationWithComment("reader", "//foobar", nil,
   192  					namespace.AllowedRelation("foos/user", "..."),
   193  					namespace.AllowedPublicNamespace("foos/user"),
   194  					namespace.AllowedRelation("foos/group", "member"),
   195  					namespace.AllowedRelationWithCaveat("foos/user", "...", namespace.AllowedCaveat("somecaveat")),
   196  					namespace.AllowedRelationWithCaveat("foos/group", "member", namespace.AllowedCaveat("somecaveat")),
   197  					namespace.AllowedPublicNamespaceWithCaveat("foos/user", namespace.AllowedCaveat("somecaveat")),
   198  				),
   199  				namespace.MustRelation("read", namespace.Union(
   200  					namespace.ComputedUserset("reader"),
   201  					namespace.ComputedUserset("owner"),
   202  				)),
   203  			),
   204  			`/** Some comment goes here */
   205  definition foos/document {
   206  	relation owner: foos/user
   207  
   208  	// foobar
   209  	relation reader: foos/user | foos/user:* | foos/group#member | foos/user with somecaveat | foos/group#member with somecaveat | foos/user:* with somecaveat
   210  	permission read = reader + owner
   211  }`,
   212  			true,
   213  		},
   214  	}
   215  
   216  	for _, test := range tests {
   217  		test := test
   218  		t.Run(test.name, func(t *testing.T) {
   219  			require := require.New(t)
   220  			source, ok, err := GenerateSource(test.input)
   221  			require.NoError(err)
   222  			require.Equal(test.expected, source)
   223  			require.Equal(test.okay, ok)
   224  		})
   225  	}
   226  }
   227  
   228  func TestFormatting(t *testing.T) {
   229  	type formattingTest struct {
   230  		name     string
   231  		input    string
   232  		expected string
   233  	}
   234  
   235  	tests := []formattingTest{
   236  		{
   237  			"empty",
   238  			"definition foos/test {}",
   239  			"definition foos/test {}",
   240  		},
   241  		{
   242  			"with comment",
   243  			`/** some def */definition foos/test {}`,
   244  			`/** some def */
   245  definition foos/test {}`,
   246  		},
   247  		{
   248  			"with rel comment",
   249  			`/** some def */definition foos/test {
   250  
   251  				// some rel
   252  				relation somerel: foos/bars;
   253  			}`,
   254  			`/** some def */
   255  definition foos/test {
   256  	// some rel
   257  	relation somerel: foos/bars
   258  }`,
   259  		},
   260  		{
   261  			"with multiple rel comment",
   262  			`/** some def */definition foos/test {
   263  
   264  				// some rel
   265  				/* another comment */
   266  				relation somerel: foos/bars;
   267  			}`,
   268  			`/** some def */
   269  definition foos/test {
   270  	// some rel
   271  	/* another comment */
   272  	relation somerel: foos/bars
   273  }`,
   274  		},
   275  		{
   276  			"with multiple rels with comment",
   277  			`/** some def */definition foos/test {
   278  
   279  				// some rel
   280  				relation somerel: foos/bars;
   281  				// another perm
   282  				permission someperm = somerel
   283  			}`,
   284  			`/** some def */
   285  definition foos/test {
   286  	// some rel
   287  	relation somerel: foos/bars
   288  
   289  	// another perm
   290  	permission someperm = somerel
   291  }`,
   292  		},
   293  
   294  		{
   295  			"becomes single line comment",
   296  			`definition foos/test {
   297  				/**
   298  				 * hi there
   299  				 */
   300  				relation somerel: foos/bars;
   301  			}`,
   302  			`definition foos/test {
   303  	/** hi there */
   304  	relation somerel: foos/bars
   305  }`,
   306  		},
   307  
   308  		{
   309  			"full example",
   310  			`
   311  /** some cool caveat */
   312  caveat foos/somecaveat(someParam int, anotherParam bool) {
   313  						someParam == 42 &&
   314  				anotherParam
   315  }
   316  
   317  /** the document */
   318  definition foos/document {
   319  	/** some super long comment goes here and therefore should be made into a multiline comment */
   320  	relation reader: foos/user | foos/user:* | foos/user with foos/somecaveat
   321  
   322  	/** multiline
   323  comment */
   324  	relation  writer: foos/user
   325  
   326  	// writers are also readers
   327  	permission read = reader + writer + another
   328  	permission write = writer
   329  	permission minus = rela - relb - relc
   330  }
   331  `,
   332  			`/** some cool caveat */
   333  caveat foos/somecaveat(anotherParam bool, someParam int) {
   334  	someParam == 42 && anotherParam
   335  }
   336  
   337  /** the document */
   338  definition foos/document {
   339  	/**
   340  	 * some super long comment goes here and therefore should be made into a multiline comment
   341  	 */
   342  	relation reader: foos/user | foos/user:* | foos/user with foos/somecaveat
   343  
   344  	/**
   345  	 * multiline
   346  	 * comment
   347  	 */
   348  	relation writer: foos/user
   349  
   350  	// writers are also readers
   351  	permission read = reader + writer + another
   352  	permission write = writer
   353  	permission minus = (rela - relb) - relc
   354  }`,
   355  		},
   356  	}
   357  
   358  	for _, test := range tests {
   359  		test := test
   360  		t.Run(test.name, func(t *testing.T) {
   361  			require := require.New(t)
   362  			compiled, err := compiler.Compile(compiler.InputSchema{
   363  				Source:       input.Source(test.name),
   364  				SchemaString: test.input,
   365  			}, compiler.AllowUnprefixedObjectType())
   366  			require.NoError(err)
   367  
   368  			source, _, err := GenerateSchema(compiled.OrderedDefinitions)
   369  			require.NoError(err)
   370  			require.Equal(test.expected, source)
   371  		})
   372  	}
   373  }