go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/invocations/read_test.go (about)

     1  // Copyright 2020 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 invocations
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"cloud.google.com/go/spanner"
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/protobuf/types/known/structpb"
    24  
    25  	"go.chromium.org/luci/common/clock/testclock"
    26  	"go.chromium.org/luci/resultdb/internal/spanutil"
    27  	"go.chromium.org/luci/resultdb/internal/testutil"
    28  	"go.chromium.org/luci/resultdb/pbutil"
    29  	pb "go.chromium.org/luci/resultdb/proto/v1"
    30  	"go.chromium.org/luci/server/span"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  	. "go.chromium.org/luci/common/testing/assertions"
    34  )
    35  
    36  func TestRead(t *testing.T) {
    37  	Convey(`Read`, t, func() {
    38  		ctx := testutil.SpannerTestContext(t)
    39  		start := testclock.TestRecentTimeUTC
    40  
    41  		properties := &structpb.Struct{
    42  			Fields: map[string]*structpb.Value{
    43  				"key_1": structpb.NewStringValue("value_1"),
    44  				"key_2": structpb.NewStructValue(&structpb.Struct{
    45  					Fields: map[string]*structpb.Value{
    46  						"child_key": structpb.NewNumberValue(1),
    47  					},
    48  				}),
    49  			},
    50  		}
    51  		sources := &pb.Sources{
    52  			GitilesCommit: &pb.GitilesCommit{
    53  				Host:       "chromium.googlesource.com",
    54  				Project:    "infra/infra",
    55  				Ref:        "refs/heads/main",
    56  				CommitHash: "1234567890abcdefabcd1234567890abcdefabcd",
    57  				Position:   567,
    58  			},
    59  			Changelists: []*pb.GerritChange{
    60  				{
    61  					Host:     "chromium-review.googlesource.com",
    62  					Project:  "infra/luci-go",
    63  					Change:   12345,
    64  					Patchset: 321,
    65  				},
    66  			},
    67  			IsDirty: true,
    68  		}
    69  
    70  		// Insert some Invocations.
    71  		testutil.MustApply(ctx,
    72  			insertInvocation("including", map[string]any{
    73  				"State":          pb.Invocation_ACTIVE,
    74  				"CreateTime":     start,
    75  				"Deadline":       start.Add(time.Hour),
    76  				"Properties":     spanutil.Compressed(pbutil.MustMarshal(properties)),
    77  				"Sources":        spanutil.Compressed(pbutil.MustMarshal(sources)),
    78  				"InheritSources": spanner.NullBool{Bool: true, Valid: true},
    79  				"BaselineId":     "try:linux-rel",
    80  			}),
    81  			insertInvocation("included0", nil),
    82  			insertInvocation("included1", nil),
    83  			insertInclusion("including", "included0"),
    84  			insertInclusion("including", "included1"),
    85  		)
    86  
    87  		ctx, cancel := span.ReadOnlyTransaction(ctx)
    88  		defer cancel()
    89  
    90  		// Fetch back the top-level Invocation.
    91  		inv, err := Read(ctx, "including")
    92  		So(err, ShouldBeNil)
    93  		So(inv, ShouldResembleProto, &pb.Invocation{
    94  			Name:                "invocations/including",
    95  			State:               pb.Invocation_ACTIVE,
    96  			CreateTime:          pbutil.MustTimestampProto(start),
    97  			Deadline:            pbutil.MustTimestampProto(start.Add(time.Hour)),
    98  			IncludedInvocations: []string{"invocations/included0", "invocations/included1"},
    99  			Properties:          properties,
   100  			SourceSpec: &pb.SourceSpec{
   101  				Inherit: true,
   102  				Sources: sources,
   103  			},
   104  			BaselineId: "try:linux-rel",
   105  		})
   106  	})
   107  }
   108  
   109  func TestReadBatch(t *testing.T) {
   110  	Convey(`TestReadBatch`, t, func() {
   111  		ctx := testutil.SpannerTestContext(t)
   112  
   113  		testutil.MustApply(ctx,
   114  			insertInvocation("inv0", nil),
   115  			insertInvocation("inv1", nil),
   116  			insertInvocation("inv2", nil),
   117  		)
   118  
   119  		ctx, cancel := span.ReadOnlyTransaction(ctx)
   120  		defer cancel()
   121  
   122  		Convey(`One name`, func() {
   123  			invs, err := ReadBatch(ctx, NewIDSet("inv1"))
   124  			So(err, ShouldBeNil)
   125  			So(invs, ShouldHaveLength, 1)
   126  			So(invs, ShouldContainKey, ID("inv1"))
   127  			So(invs["inv1"].Name, ShouldEqual, "invocations/inv1")
   128  			So(invs["inv1"].State, ShouldEqual, pb.Invocation_FINALIZED)
   129  		})
   130  
   131  		Convey(`Two names`, func() {
   132  			invs, err := ReadBatch(ctx, NewIDSet("inv0", "inv1"))
   133  			So(err, ShouldBeNil)
   134  			So(invs, ShouldHaveLength, 2)
   135  			So(invs, ShouldContainKey, ID("inv0"))
   136  			So(invs, ShouldContainKey, ID("inv1"))
   137  			So(invs["inv0"].Name, ShouldEqual, "invocations/inv0")
   138  			So(invs["inv0"].State, ShouldEqual, pb.Invocation_FINALIZED)
   139  		})
   140  
   141  		Convey(`Not found`, func() {
   142  			_, err := ReadBatch(ctx, NewIDSet("inv0", "x"))
   143  			So(err, ShouldErrLike, `invocations/x not found`)
   144  		})
   145  	})
   146  }
   147  
   148  func TestQueryRealms(t *testing.T) {
   149  	Convey(`TestQueryRealms`, t, func() {
   150  		ctx := testutil.SpannerTestContext(t)
   151  
   152  		Convey(`Works`, func() {
   153  			testutil.MustApply(ctx,
   154  				insertInvocation("inv0", map[string]any{"Realm": "0"}),
   155  				insertInvocation("inv1", map[string]any{"Realm": "1"}),
   156  				insertInvocation("inv2", map[string]any{"Realm": "2"}),
   157  			)
   158  
   159  			realms, err := QueryRealms(span.Single(ctx), NewIDSet("inv0", "inv1", "inv2"))
   160  			So(err, ShouldBeNil)
   161  			So(realms, ShouldResemble, map[ID]string{
   162  				"inv0": "0",
   163  				"inv1": "1",
   164  				"inv2": "2",
   165  			})
   166  		})
   167  		Convey(`Valid with missing invocation `, func() {
   168  			testutil.MustApply(ctx,
   169  				insertInvocation("inv0", map[string]any{"Realm": "0"}),
   170  			)
   171  
   172  			realms, err := QueryRealms(span.Single(ctx), NewIDSet("inv0", "inv1"))
   173  			So(err, ShouldBeNil)
   174  			So(realms, ShouldResemble, map[ID]string{
   175  				"inv0": "0",
   176  			})
   177  		})
   178  	})
   179  }
   180  
   181  func TestReadRealms(t *testing.T) {
   182  	Convey(`TestReadRealms`, t, func() {
   183  		ctx := testutil.SpannerTestContext(t)
   184  
   185  		Convey(`Works`, func() {
   186  			testutil.MustApply(ctx,
   187  				insertInvocation("inv0", map[string]any{"Realm": "0"}),
   188  				insertInvocation("inv1", map[string]any{"Realm": "1"}),
   189  				insertInvocation("inv2", map[string]any{"Realm": "2"}),
   190  			)
   191  
   192  			realms, err := ReadRealms(span.Single(ctx), NewIDSet("inv0", "inv1", "inv2"))
   193  			So(err, ShouldBeNil)
   194  			So(realms, ShouldResemble, map[ID]string{
   195  				"inv0": "0",
   196  				"inv1": "1",
   197  				"inv2": "2",
   198  			})
   199  		})
   200  		Convey(`NotFound`, func() {
   201  			testutil.MustApply(ctx,
   202  				insertInvocation("inv0", map[string]any{"Realm": "0"}),
   203  			)
   204  
   205  			_, err := ReadRealms(span.Single(ctx), NewIDSet("inv0", "inv1"))
   206  			So(err, ShouldHaveAppStatus, codes.NotFound, "invocations/inv1 not found")
   207  		})
   208  	})
   209  }
   210  
   211  func TestReadSubmitted(t *testing.T) {
   212  	Convey(`TestReadSubmitted`, t, func() {
   213  		ctx := testutil.SpannerTestContext(t)
   214  
   215  		Convey(`Valid`, func() {
   216  			testutil.MustApply(ctx,
   217  				insertInvocation("inv0", map[string]any{"Submitted": true}),
   218  			)
   219  
   220  			submitted, err := ReadSubmitted(span.Single(ctx), ID("inv0"))
   221  			So(err, ShouldBeNil)
   222  			So(submitted, ShouldBeTrue)
   223  		})
   224  
   225  		Convey(`Not Found`, func() {
   226  			testutil.MustApply(ctx,
   227  				insertInvocation("inv0", map[string]any{"Submitted": true}),
   228  			)
   229  			_, err := ReadSubmitted(span.Single(ctx), ID("inv1"))
   230  			So(err, ShouldHaveAppStatus, codes.NotFound, "invocations/inv1 not found")
   231  		})
   232  
   233  		Convey(`Nil`, func() {
   234  			testutil.MustApply(ctx,
   235  				insertInvocation("inv0", map[string]any{}),
   236  			)
   237  			submitted, err := ReadSubmitted(span.Single(ctx), ID("inv0"))
   238  
   239  			So(err, ShouldBeNil)
   240  			So(submitted, ShouldBeFalse)
   241  		})
   242  	})
   243  }