go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/services/recorder/update_included_invocations_test.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package recorder
    16  
    17  import (
    18  	"testing"
    19  
    20  	"google.golang.org/grpc/codes"
    21  	"google.golang.org/grpc/metadata"
    22  
    23  	"go.chromium.org/luci/server/auth"
    24  	"go.chromium.org/luci/server/auth/authtest"
    25  
    26  	"go.chromium.org/luci/resultdb/internal/invocations"
    27  	"go.chromium.org/luci/resultdb/internal/testutil"
    28  	"go.chromium.org/luci/resultdb/internal/testutil/insert"
    29  	pb "go.chromium.org/luci/resultdb/proto/v1"
    30  
    31  	. "github.com/smartystreets/goconvey/convey"
    32  	. "go.chromium.org/luci/common/testing/assertions"
    33  )
    34  
    35  func TestValidateUpdateIncludedInvocationsRequest(t *testing.T) {
    36  	t.Parallel()
    37  	Convey(`TestValidateUpdateIncludedInvocationsRequest`, t, func() {
    38  		Convey(`Valid`, func() {
    39  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    40  				IncludingInvocation: "invocations/a",
    41  				AddInvocations:      []string{"invocations/b"},
    42  				RemoveInvocations:   []string{"invocations/c"},
    43  			})
    44  			So(err, ShouldBeNil)
    45  		})
    46  
    47  		Convey(`Invalid including_invocation`, func() {
    48  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    49  				IncludingInvocation: "x",
    50  				AddInvocations:      []string{"invocations/b"},
    51  				RemoveInvocations:   []string{"invocations/c"},
    52  			})
    53  			So(err, ShouldErrLike, `including_invocation: does not match`)
    54  		})
    55  		Convey(`Invalid add_invocations`, func() {
    56  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    57  				IncludingInvocation: "invocations/a",
    58  				AddInvocations:      []string{"x"},
    59  				RemoveInvocations:   []string{"invocations/c"},
    60  			})
    61  			So(err, ShouldErrLike, `add_invocations: "x": does not match`)
    62  		})
    63  		Convey(`Invalid remove_invocations`, func() {
    64  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    65  				IncludingInvocation: "invocations/a",
    66  				AddInvocations:      []string{"invocations/b"},
    67  				RemoveInvocations:   []string{"x"},
    68  			})
    69  			So(err, ShouldErrLike, `remove_invocations: "x": does not match`)
    70  		})
    71  		Convey(`Include itself`, func() {
    72  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    73  				IncludingInvocation: "invocations/a",
    74  				AddInvocations:      []string{"invocations/a"},
    75  				RemoveInvocations:   []string{"invocations/c"},
    76  			})
    77  			So(err, ShouldErrLike, `cannot include itself`)
    78  		})
    79  		Convey(`Add and remove same invocation`, func() {
    80  			err := validateUpdateIncludedInvocationsRequest(&pb.UpdateIncludedInvocationsRequest{
    81  				IncludingInvocation: "invocations/a",
    82  				AddInvocations:      []string{"invocations/b"},
    83  				RemoveInvocations:   []string{"invocations/b"},
    84  			})
    85  			So(err, ShouldErrLike, `cannot add and remove the same invocation(s) at the same time: ["invocations/b"]`)
    86  		})
    87  	})
    88  }
    89  
    90  func TestUpdateIncludedInvocations(t *testing.T) {
    91  	Convey(`TestIncludedInvocations`, t, func() {
    92  		ctx := auth.WithState(testutil.SpannerTestContext(t), &authtest.FakeState{
    93  			Identity: "user:someone@example.com",
    94  			IdentityPermissions: []authtest.RealmPermission{
    95  				{Realm: "testproject:testrealm", Permission: permIncludeInvocation},
    96  			},
    97  		})
    98  		recorder := newTestRecorderServer()
    99  
   100  		token, err := generateInvocationToken(ctx, "including")
   101  		So(err, ShouldBeNil)
   102  		ctx = metadata.NewIncomingContext(ctx, metadata.Pairs(pb.UpdateTokenMetadataKey, token))
   103  
   104  		assertIncluded := func(includedInvID invocations.ID) {
   105  			var throwAway invocations.ID
   106  			testutil.MustReadRow(ctx, "IncludedInvocations", invocations.InclusionKey("including", includedInvID), map[string]any{
   107  				"IncludedInvocationID": &throwAway,
   108  			})
   109  		}
   110  		assertNotIncluded := func(includedInvID invocations.ID) {
   111  			var throwAway invocations.ID
   112  			testutil.MustNotFindRow(ctx, "IncludedInvocations", invocations.InclusionKey("including", includedInvID), map[string]any{
   113  				"IncludedInvocationID": &throwAway,
   114  			})
   115  		}
   116  
   117  		Convey(`Invalid request`, func() {
   118  			_, err := recorder.UpdateIncludedInvocations(ctx, &pb.UpdateIncludedInvocationsRequest{})
   119  			So(err, ShouldHaveAppStatus, codes.InvalidArgument, `bad request: including_invocation: unspecified`)
   120  		})
   121  
   122  		Convey(`With valid request`, func() {
   123  			req := &pb.UpdateIncludedInvocationsRequest{
   124  				IncludingInvocation: "invocations/including",
   125  				AddInvocations: []string{
   126  					"invocations/included",
   127  					"invocations/included2",
   128  				},
   129  				RemoveInvocations: []string{
   130  					"invocations/toberemoved",
   131  					"invocations/neverexisted",
   132  				},
   133  			}
   134  
   135  			Convey(`No including invocation`, func() {
   136  				testutil.MustApply(ctx,
   137  					insert.Invocation("included", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   138  					insert.Invocation("included2", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   139  				)
   140  				_, err := recorder.UpdateIncludedInvocations(ctx, req)
   141  				So(err, ShouldHaveAppStatus, codes.NotFound, `invocations/including not found`)
   142  			})
   143  
   144  			Convey(`With existing inclusion`, func() {
   145  				testutil.MustApply(ctx,
   146  					insert.Invocation("including", pb.Invocation_ACTIVE, nil),
   147  					insert.Invocation("toberemoved", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   148  				)
   149  				_, err := recorder.UpdateIncludedInvocations(ctx, &pb.UpdateIncludedInvocationsRequest{
   150  					IncludingInvocation: "invocations/including",
   151  					AddInvocations:      []string{"invocations/toberemoved"},
   152  				})
   153  				So(err, ShouldBeNil)
   154  				assertIncluded("toberemoved")
   155  
   156  				Convey(`No included invocation`, func() {
   157  					_, err := recorder.UpdateIncludedInvocations(ctx, req)
   158  					So(err, ShouldHaveAppStatus, codes.NotFound, `invocations/included`)
   159  				})
   160  
   161  				Convey(`Leaking disallowed`, func() {
   162  					testutil.MustApply(ctx,
   163  						insert.Invocation("included", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   164  						insert.Invocation("included2", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:secretrealm"}),
   165  					)
   166  
   167  					_, err := recorder.UpdateIncludedInvocations(ctx, req)
   168  					So(err, ShouldHaveAppStatus, codes.PermissionDenied, `caller does not have permission resultdb.invocations.include in realm of invocation included2`)
   169  					assertNotIncluded("included2")
   170  				})
   171  
   172  				Convey(`Success - idempotent`, func() {
   173  					testutil.MustApply(ctx,
   174  						insert.Invocation("included", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   175  						insert.Invocation("included2", pb.Invocation_FINALIZED, map[string]any{"Realm": "testproject:testrealm"}),
   176  					)
   177  
   178  					_, err := recorder.UpdateIncludedInvocations(ctx, req)
   179  					So(err, ShouldBeNil)
   180  					assertIncluded("included")
   181  					assertIncluded("included2")
   182  					assertNotIncluded("toberemoved")
   183  
   184  					_, err = recorder.UpdateIncludedInvocations(ctx, req)
   185  					So(err, ShouldBeNil)
   186  					assertIncluded("included")
   187  					assertIncluded("included2")
   188  					assertNotIncluded("toberemoved")
   189  				})
   190  			})
   191  		})
   192  	})
   193  }