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 }