internal/gomote: add swarming list directory

This change adds the list directory endpoint to the swarming gomote
implementation.

Fixes golang/go#63775

Change-Id: If2049ebef15472b0ccdebed212138306cb3e3f37
Reviewed-on: https://go-review.googlesource.com/c/build/+/537902
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Carlos Amedee <carlos@golang.org>
diff --git a/internal/gomote/swarming_test.go b/internal/gomote/swarming_test.go
index 2c4f15b..f422fc5 100644
--- a/internal/gomote/swarming_test.go
+++ b/internal/gomote/swarming_test.go
@@ -487,6 +487,91 @@
 	}
 }
 
+func TestSwarmingListDirectory(t *testing.T) {
+	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
+	client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
+	gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
+	if _, err := client.ListDirectory(ctx, &protos.ListDirectoryRequest{
+		GomoteId:  gomoteID,
+		Directory: "/foo",
+	}); err != nil {
+		t.Fatalf("client.RemoveFiles(ctx, req) = response, %s; want no error", err)
+	}
+}
+
+func TestSwarmingListDirectoryError(t *testing.T) {
+	// This test will create a gomote instance and attempt to call ListDirectory.
+	// If overrideID is set to true, the test will use a different gomoteID than
+	// the one created for the test.
+	testCases := []struct {
+		desc       string
+		ctx        context.Context
+		overrideID bool
+		gomoteID   string // Used iff overrideID is true.
+		directory  string
+		recursive  bool
+		skipFiles  []string
+		digest     bool
+		wantCode   codes.Code
+	}{
+		{
+			desc:     "unauthenticated request",
+			ctx:      context.Background(),
+			wantCode: codes.Unauthenticated,
+		},
+		{
+			desc:       "missing gomote id",
+			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
+			overrideID: true,
+			gomoteID:   "",
+			wantCode:   codes.InvalidArgument,
+		},
+		{
+			desc:     "missing directory",
+			ctx:      access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP()),
+			wantCode: codes.InvalidArgument,
+		},
+		{
+			desc:       "gomote does not exist",
+			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
+			overrideID: true,
+			gomoteID:   "chucky",
+			directory:  "/foo",
+			wantCode:   codes.NotFound,
+		},
+		{
+			desc:       "wrong gomote id",
+			ctx:        access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAPWithUser("foo", "bar")),
+			overrideID: false,
+			directory:  "/foo",
+			wantCode:   codes.PermissionDenied,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
+			gomoteID := mustCreateSwarmingInstance(t, client, fakeIAP())
+			if tc.overrideID {
+				gomoteID = tc.gomoteID
+			}
+			req := &protos.ListDirectoryRequest{
+				GomoteId:  gomoteID,
+				Directory: tc.directory,
+				Recursive: false,
+				SkipFiles: []string{},
+				Digest:    false,
+			}
+			got, err := client.ListDirectory(tc.ctx, req)
+			if err != nil && status.Code(err) != tc.wantCode {
+				t.Fatalf("unexpected error: %s; want %s", err, tc.wantCode)
+			}
+			if err == nil {
+				t.Fatalf("client.RemoveFiles(ctx, %v) = %v, nil; want error", req, got)
+			}
+		})
+	}
+}
+
 func TestSwarmingListInstance(t *testing.T) {
 	client := setupGomoteSwarmingTest(t, context.Background(), mockSwarmClientSimple())
 	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())