go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth_service/impl/servers/authdb/server_test.go (about)

     1  // Copyright 2022 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 authdb
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"strconv"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/julienschmidt/httprouter"
    29  	"google.golang.org/grpc/codes"
    30  	"google.golang.org/protobuf/types/known/timestamppb"
    31  
    32  	"go.chromium.org/luci/gae/impl/memory"
    33  	"go.chromium.org/luci/gae/service/datastore"
    34  	"go.chromium.org/luci/server/router"
    35  
    36  	"go.chromium.org/luci/auth_service/api/rpcpb"
    37  	"go.chromium.org/luci/auth_service/impl/model"
    38  
    39  	. "github.com/smartystreets/goconvey/convey"
    40  	. "go.chromium.org/luci/common/testing/assertions"
    41  )
    42  
    43  func TestAuthDBServing(t *testing.T) {
    44  	testTS := time.Date(2021, time.August, 16, 15, 20, 0, 0, time.UTC)
    45  	testTSMicro := testTS.UnixNano() / 1000
    46  	testHash := "SHA256-hash"
    47  	testDeflated := []byte("deflated-groups")
    48  
    49  	testAuthDBSnapshotLatest := func(rid int64) *model.AuthDBSnapshotLatest {
    50  		return &model.AuthDBSnapshotLatest{
    51  			Kind:         "AuthDBSnapshotLatest",
    52  			ID:           "latest",
    53  			AuthDBRev:    rid,
    54  			AuthDBSha256: testHash,
    55  			ModifiedTS:   testTS,
    56  		}
    57  	}
    58  
    59  	testAuthDBSnapshot := func(rid int64) *model.AuthDBSnapshot {
    60  		return &model.AuthDBSnapshot{
    61  			Kind:           "AuthDBSnapshot",
    62  			ID:             rid,
    63  			AuthDBDeflated: testDeflated,
    64  			AuthDBSha256:   testHash,
    65  			CreatedTS:      testTS,
    66  		}
    67  	}
    68  
    69  	legacyCall := func(server Server, ctx context.Context, rid int64, skipBody bool) []byte {
    70  		rw := httptest.NewRecorder()
    71  		var revIDStr string
    72  		var sb string
    73  		if rid == 0 {
    74  			revIDStr = "latest"
    75  		} else {
    76  			revIDStr = strconv.FormatInt(rid, 10)
    77  		}
    78  
    79  		if skipBody {
    80  			sb = "1"
    81  		} else {
    82  			sb = "0"
    83  		}
    84  		rctx := &router.Context{
    85  			Request: (&http.Request{
    86  				URL: &url.URL{
    87  					RawQuery: fmt.Sprintf("skip_body=%s", sb),
    88  				},
    89  			}).WithContext(ctx),
    90  			Params: []httprouter.Param{
    91  				{Key: "revID", Value: revIDStr},
    92  			},
    93  			Writer: rw,
    94  		}
    95  		err := server.HandleLegacyAuthDBServing(rctx)
    96  		So(err, ShouldBeNil)
    97  		return rw.Body.Bytes()
    98  	}
    99  
   100  	t.Parallel()
   101  	ctx := memory.Use(context.Background())
   102  
   103  	Convey("Testing pRPC API", t, func() {
   104  		server := Server{}
   105  		So(datastore.Put(ctx,
   106  			testAuthDBSnapshot(1),
   107  			testAuthDBSnapshot(2),
   108  			testAuthDBSnapshot(3),
   109  			testAuthDBSnapshot(4)), ShouldBeNil)
   110  
   111  		Convey("Testing Error Codes", func() {
   112  			requestNegative := &rpcpb.GetSnapshotRequest{
   113  				Revision: -1,
   114  			}
   115  			_, err := server.GetSnapshot(ctx, requestNegative)
   116  			So(err, ShouldHaveGRPCStatus, codes.InvalidArgument)
   117  
   118  			requestNotPresent := &rpcpb.GetSnapshotRequest{
   119  				Revision: 42,
   120  			}
   121  			_, err = server.GetSnapshot(ctx, requestNotPresent)
   122  			So(err, ShouldHaveGRPCStatus, codes.NotFound)
   123  
   124  			requestNotPresent.Revision, err = getLatestRevision(ctx, requestNotPresent)
   125  			So(err, ShouldHaveGRPCStatus, codes.NotFound)
   126  		})
   127  
   128  		Convey("Testing valid revision ID", func() {
   129  			request := &rpcpb.GetSnapshotRequest{
   130  				Revision: 2,
   131  			}
   132  			snapshot, err := server.GetSnapshot(ctx, request)
   133  			So(err, ShouldBeNil)
   134  			So(snapshot, ShouldResembleProto, &rpcpb.Snapshot{
   135  				AuthDbRev:      2,
   136  				AuthDbSha256:   testHash,
   137  				AuthDbDeflated: testDeflated,
   138  				CreatedTs:      timestamppb.New(testTS),
   139  			})
   140  		})
   141  
   142  		Convey("Testing GetSnapshotLatest", func() {
   143  			So(datastore.Put(ctx, testAuthDBSnapshotLatest(4)), ShouldBeNil)
   144  			request := &rpcpb.GetSnapshotRequest{
   145  				Revision: 0,
   146  			}
   147  			latestSnapshot, err := server.GetSnapshot(ctx, request)
   148  			So(err, ShouldBeNil)
   149  			So(latestSnapshot, ShouldResembleProto, &rpcpb.Snapshot{
   150  				AuthDbRev:      4,
   151  				AuthDbSha256:   testHash,
   152  				AuthDbDeflated: testDeflated,
   153  				CreatedTs:      timestamppb.New(testTS),
   154  			})
   155  		})
   156  
   157  		Convey("Testing skipbody", func() {
   158  			requestSnapshotSkip := &rpcpb.GetSnapshotRequest{
   159  				Revision: 3,
   160  				SkipBody: true,
   161  			}
   162  			snapshot, err := server.GetSnapshot(ctx, requestSnapshotSkip)
   163  			So(err, ShouldBeNil)
   164  			So(snapshot, ShouldResembleProto, &rpcpb.Snapshot{
   165  				AuthDbRev:    3,
   166  				AuthDbSha256: testHash,
   167  				CreatedTs:    timestamppb.New(testTS),
   168  			})
   169  
   170  			So(datastore.Put(ctx, testAuthDBSnapshotLatest(4)), ShouldBeNil)
   171  			requestLatestSkip := &rpcpb.GetSnapshotRequest{
   172  				Revision: 0,
   173  				SkipBody: true,
   174  			}
   175  			latest, err := server.GetSnapshot(ctx, requestLatestSkip)
   176  			So(err, ShouldBeNil)
   177  			So(latest, ShouldResembleProto, &rpcpb.Snapshot{
   178  				AuthDbRev:    4,
   179  				AuthDbSha256: testHash,
   180  				CreatedTs:    timestamppb.New(testTS),
   181  			})
   182  		})
   183  
   184  		Convey("Testing GetSnapshot with sharded DB in datastore.", func() {
   185  			shardedSnapshot := &model.AuthDBSnapshot{
   186  				Kind: "AuthDBSnapshot",
   187  				ID:   42,
   188  				ShardIDs: []string{
   189  					"42:shard-1",
   190  					"42:shard-2",
   191  				},
   192  				AuthDBSha256: testHash,
   193  				CreatedTS:    testTS,
   194  			}
   195  
   196  			shard1 := &model.AuthDBShard{
   197  				Kind: "AuthDBShard",
   198  				ID:   "42:shard-1",
   199  				Blob: []byte("shard-1-groups"),
   200  			}
   201  			shard2 := &model.AuthDBShard{
   202  				Kind: "AuthDBShard",
   203  				ID:   "42:shard-2",
   204  				Blob: []byte("shard-2-groups"),
   205  			}
   206  			So(datastore.Put(ctx, shard1, shard2, shardedSnapshot), ShouldBeNil)
   207  			requestSnapshot := &rpcpb.GetSnapshotRequest{
   208  				Revision: 42,
   209  			}
   210  			snapshot, err := server.GetSnapshot(ctx, requestSnapshot)
   211  			So(err, ShouldBeNil)
   212  			So(snapshot, ShouldResembleProto, &rpcpb.Snapshot{
   213  				AuthDbRev:      42,
   214  				AuthDbSha256:   testHash,
   215  				AuthDbDeflated: []byte("shard-1-groupsshard-2-groups"),
   216  				CreatedTs:      timestamppb.New(testTS),
   217  			})
   218  		})
   219  	})
   220  
   221  	Convey("Testing legacy API Server with JSON response", t, func() {
   222  		server := Server{}
   223  		type TestSnapshotJSON struct {
   224  			AuthDBRev      int64  `json:"auth_db_rev"`
   225  			AuthDBDeflated []byte `json:"deflated_body,omitempty"`
   226  			AuthDBSha256   string `json:"sha256"`
   227  			CreatedTS      int64  `json:"created_ts"`
   228  		}
   229  		expectedJSON := func(rid int64, sb bool) ([]byte, error) {
   230  			if sb {
   231  				testDeflated = []byte{}
   232  			}
   233  			return json.Marshal(map[string]any{
   234  				"snapshot": TestSnapshotJSON{
   235  					AuthDBRev:      rid,
   236  					AuthDBDeflated: testDeflated,
   237  					AuthDBSha256:   testHash,
   238  					CreatedTS:      testTSMicro,
   239  				},
   240  			})
   241  		}
   242  
   243  		So(datastore.Put(ctx,
   244  			testAuthDBSnapshot(1),
   245  			testAuthDBSnapshot(2),
   246  			testAuthDBSnapshot(3),
   247  			testAuthDBSnapshot(4)), ShouldBeNil)
   248  
   249  		Convey("Testing GetSnapshotLegacy skipBody=true", func() {
   250  			rid := int64(3)
   251  			skipBody := true
   252  			actualBlob := legacyCall(server, ctx, rid, skipBody)
   253  			expectedBlob, err := expectedJSON(rid, skipBody)
   254  			So(err, ShouldBeNil)
   255  			So(actualBlob, ShouldResemble, expectedBlob)
   256  		})
   257  
   258  		Convey("Testing GetSnapshotLegacy skipBody=false", func() {
   259  			rid := int64(3)
   260  			skipBody := false
   261  			actualBlob := legacyCall(server, ctx, rid, skipBody)
   262  			expectedBlob, err := expectedJSON(rid, skipBody)
   263  			So(err, ShouldBeNil)
   264  			So(actualBlob, ShouldResemble, expectedBlob)
   265  		})
   266  
   267  		Convey("Testing GetSnapshotLatestLegacy skipBody=true", func() {
   268  			rid := int64(4)
   269  			skipBody := true
   270  			actualBlob := legacyCall(server, ctx, rid, skipBody)
   271  			expectedBlob, err := expectedJSON(rid, skipBody)
   272  			So(err, ShouldBeNil)
   273  			So(actualBlob, ShouldResemble, expectedBlob)
   274  
   275  		})
   276  
   277  		Convey("Testing GetSnapshotLatestLegacy skipBody=false", func() {
   278  			rid := int64(4)
   279  			skipBody := false
   280  			actualBlob := legacyCall(server, ctx, rid, skipBody)
   281  			expectedBlob, err := expectedJSON(rid, skipBody)
   282  			So(err, ShouldBeNil)
   283  			So(actualBlob, ShouldResemble, expectedBlob)
   284  		})
   285  	})
   286  }