blob: 0867e71b81da23e433fbeeadda475e7f7458b2f3 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rsmsg
import (
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/testing/protocmp"
rspb "google.golang.org/genproto/googleapis/devtools/resultstore/v2"
_ "github.com/bazelbuild/rsclient/internal/pkg/testsetup"
)
func TestEncodeID(t *testing.T) {
tests := []struct {
input string
escapeChar string
want string
}{
{"abc-123_xyz", "%", "abc-123_xyz"},
{"id with spaces", "%", "id%20with%20spaces"},
{"id/with/slashes", "%", "id%2Fwith%2Fslashes"},
{"id.with.dots", "%", "id%2Ewith%2Edots"},
{"abc-123_xyz", "_", "abc-123__xyz"}, // _ is escaped as __
{"id with spaces", "_", "id_20with_20spaces"},
}
for _, tt := range tests {
got := EncodeID(tt.input, tt.escapeChar)
if got != tt.want {
t.Errorf("EncodeID(%q, %q) = %q, want %q", tt.input, tt.escapeChar, got, tt.want)
}
}
}
func TestMangleInvocation(t *testing.T) {
inv := &rspb.Invocation{
Id: &rspb.Invocation_Id{InvocationId: "id with spaces"},
Name: "projects/p/instances/i/invocations/id with spaces",
}
MangleInvocation(inv)
wantID := "id%20with%20spaces"
wantName := "projects/p/instances/i/invocations/id%20with%20spaces"
if inv.Id.InvocationId != wantID {
t.Errorf("InvocationId = %q, want %q", inv.Id.InvocationId, wantID)
}
if inv.Name != wantName {
t.Errorf("Name = %q, want %q", inv.Name, wantName)
}
}
func TestMangleTarget(t *testing.T) {
target := &rspb.Target{
Id: &rspb.Target_Id{
InvocationId: "inv id",
TargetId: "tgt id",
},
Name: "projects/p/instances/i/invocations/inv id/targets/tgt id",
}
MangleTarget(target)
wantInvID := "inv%20id"
wantTgtID := "tgt%20id"
wantName := "projects/p/instances/i/invocations/inv%20id/targets/tgt%20id"
if target.Id.InvocationId != wantInvID {
t.Errorf("InvocationId = %q, want %q", target.Id.InvocationId, wantInvID)
}
if target.Id.TargetId != wantTgtID {
t.Errorf("TargetId = %q, want %q", target.Id.TargetId, wantTgtID)
}
if target.Name != wantName {
t.Errorf("Name = %q, want %q", target.Name, wantName)
}
}
func TestMangleConfiguration(t *testing.T) {
cfg := &rspb.Configuration{
Id: &rspb.Configuration_Id{InvocationId: "inv id", ConfigurationId: "cfg id"},
Name: "projects/p/invocations/inv id/configs/cfg id",
}
MangleConfiguration(cfg)
wantInvID := "inv%20id"
wantCfgID := "cfg%20id"
wantName := "projects/p/invocations/inv%20id/configs/cfg%20id"
if cfg.Id.InvocationId != wantInvID {
t.Errorf("InvocationId = %q, want %q", cfg.Id.InvocationId, wantInvID)
}
if cfg.Id.ConfigurationId != wantCfgID {
t.Errorf("ConfigurationId = %q, want %q", cfg.Id.ConfigurationId, wantCfgID)
}
if cfg.Name != wantName {
t.Errorf("Name = %q, want %q", cfg.Name, wantName)
}
}
func TestMangleConfiguredTarget(t *testing.T) {
ct := &rspb.ConfiguredTarget{
Id: &rspb.ConfiguredTarget_Id{
InvocationId: "inv id",
TargetId: "tgt id",
ConfigurationId: "cfg id",
},
Name: "projects/p/invocations/inv id/targets/tgt id/configuredTargets/cfg id",
}
MangleConfiguredTarget(ct)
wantInvID := "inv%20id"
wantTgtID := "tgt%20id"
wantCfgID := "cfg%20id"
wantName := "projects/p/invocations/inv%20id/targets/tgt%20id/configuredTargets/cfg%20id"
if ct.Id.InvocationId != wantInvID {
t.Errorf("InvocationId = %q, want %q", ct.Id.InvocationId, wantInvID)
}
if ct.Id.TargetId != wantTgtID {
t.Errorf("TargetId = %q, want %q", ct.Id.TargetId, wantTgtID)
}
if ct.Id.ConfigurationId != wantCfgID {
t.Errorf("ConfigurationId = %q, want %q", ct.Id.ConfigurationId, wantCfgID)
}
if ct.Name != wantName {
t.Errorf("Name = %q, want %q", ct.Name, wantName)
}
}
func TestMangleAction(t *testing.T) {
action := &rspb.Action{
Id: &rspb.Action_Id{
InvocationId: "inv id",
TargetId: "tgt id",
ConfigurationId: "cfg id",
ActionId: "act id",
},
Name: "projects/p/instances/i/invocations/inv id/targets/tgt id/configuredTargets/cfg id/actions/act id",
}
MangleAction(action)
wantInvID := "inv%20id"
wantTgtID := "tgt%20id"
wantCfgID := "cfg%20id"
wantActID := "act_20id" // Actions use _ encoding
wantName := "projects/p/instances/i/invocations/inv%20id/targets/tgt%20id/configuredTargets/cfg%20id/actions/act_20id"
if action.Id.InvocationId != wantInvID {
t.Errorf("InvocationId = %q, want %q", action.Id.InvocationId, wantInvID)
}
if action.Id.TargetId != wantTgtID {
t.Errorf("TargetId = %q, want %q", action.Id.TargetId, wantTgtID)
}
if action.Id.ConfigurationId != wantCfgID {
t.Errorf("ConfigurationId = %q, want %q", action.Id.ConfigurationId, wantCfgID)
}
if action.Id.ActionId != wantActID {
t.Errorf("ActionId = %q, want %q", action.Id.ActionId, wantActID)
}
if action.Name != wantName {
t.Errorf("Name = %q, want %q", action.Name, wantName)
}
}
func TestMangleResourceIDs(t *testing.T) {
req := &rspb.UploadRequest{
Resource: &rspb.UploadRequest_Invocation{
Invocation: &rspb.Invocation{
Id: &rspb.Invocation_Id{InvocationId: "inv id"},
Name: "invocations/inv id",
},
},
}
MangleResourceIDs(req)
got := req.GetInvocation().Id.InvocationId
want := "inv%20id"
if got != want {
t.Errorf("InvocationId = %q, want %q", got, want)
}
}
func TestIDTransformer_CustomStrategy(t *testing.T) {
// A strategy that just uppercases everything.
upper := &IDTransformer{
ActionID: strings.ToUpper,
OtherID: strings.ToUpper,
}
inv := &rspb.Invocation{
Id: &rspb.Invocation_Id{InvocationId: "inv-abc"},
Name: "projects/p/invocations/inv-abc",
}
upper.Invocation(inv)
wantID := "INV-ABC"
wantName := "projects/p/invocations/INV-ABC"
if inv.Id.InvocationId != wantID {
t.Errorf("InvocationId = %q, want %q", inv.Id.InvocationId, wantID)
}
if inv.Name != wantName {
t.Errorf("Name = %q, want %q", inv.Name, wantName)
}
}
func TestMangleResourceIDs_Complex(t *testing.T) {
// Ensure that all top-level IDs are mangled correctly.
req := &rspb.UploadRequest{
Id: &rspb.UploadRequest_Id{
TargetId: "tgt id",
ConfigurationId: "cfg id",
ActionId: "act id",
},
}
MangleResourceIDs(req)
want := &rspb.UploadRequest_Id{
TargetId: "tgt%20id",
ConfigurationId: "cfg%20id",
ActionId: "act_20id",
}
if diff := cmp.Diff(want, req.Id, protocmp.Transform()); diff != "" {
t.Errorf("UploadRequest.Id mismatch (-want +got):\n%s", diff)
}
}
func TestDedupeStrings(t *testing.T) {
tests := []struct {
name string
input []string
want []string
}{
{
name: "no duplicates",
input: []string{"a", "b", "c"},
want: []string{"a", "b", "c"},
},
{
name: "duplicates removed",
input: []string{"a", "b", "a", "c", "b"},
want: []string{"a", "b", "c"},
},
{
name: "empty slice",
input: []string{},
want: []string{},
},
{
name: "nil slice",
input: nil,
want: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := DedupeStrings(tt.input)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("DedupeStrings() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestDedupeInvocationAttributes(t *testing.T) {
attr := &rspb.InvocationAttributes{
Labels: []string{"a", "b", "a"},
Users: []string{"user1", "user2", "user1"},
}
DedupeInvocationAttributes(attr)
want := &rspb.InvocationAttributes{
Labels: []string{"a", "b"},
Users: []string{"user1", "user2"},
}
if diff := cmp.Diff(want, attr, protocmp.Transform()); diff != "" {
t.Errorf("DedupeInvocationAttributes() mismatch (-want +got):\n%s", diff)
}
}
func TestComposeInvocation(t *testing.T) {
tool := &rspb.Invocation{
Id: &rspb.Invocation_Id{InvocationId: "tool-id"},
InvocationAttributes: &rspb.InvocationAttributes{
Labels: []string{"tool-label"},
},
}
partial := &rspb.Invocation{
InvocationAttributes: &rspb.InvocationAttributes{
Labels: []string{"proxy-label"},
Users: []string{"proxy-user"},
},
}
projectID := "test-project"
got := ComposeInvocation(tool, partial, projectID)
want := &rspb.Invocation{
Id: &rspb.Invocation_Id{InvocationId: "tool-id"},
InvocationAttributes: &rspb.InvocationAttributes{
ProjectId: "test-project",
Labels: []string{"tool-label", "proxy-label"},
Users: []string{"proxy-user"},
},
StatusAttributes: &rspb.StatusAttributes{
Status: rspb.Status_BUILDING,
},
}
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("ComposeInvocation() mismatch (-want +got):\n%s", diff)
}
}