From b0a7f0b43308975af6140771c753547c9e63d2fa Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 28 May 2025 11:31:15 +0100 Subject: [PATCH 1/7] Add Unit Test Job in Workflow --- .github/workflows/main.yaml | 45 +++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ce7cd04..45aa5d2 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,9 +11,50 @@ env: CLUSTER_NAME: threadit-cluster ZONE: europe-west1-b GCS_KEY: gcs-key - SERVICES: db community thread comment vote search popular + SERVICES: community thread comment vote search popular jobs: + test: + name: Run Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Run unit tests for thread-service + working-directory: code/services/thread-service + run: go test ./test/... + + - name: Run unit tests for vote-service + working-directory: code/services/vote-service + run: go test ./test/... + + - name: Run unit tests for search-service + working-directory: code/services/search-service + run: go test ./test/... + + - name: Run unit tests for popular-service + working-directory: code/services/popular-service + run: go test ./test/... + + - name: Run unit tests for db-service + working-directory: code/services/db-service + run: go test ./test/... + + - name: Run unit tests for community-service + working-directory: code/services/community-service + run: go test ./test/... + + - name: Run unit tests for comment-service + working-directory: code/services/comment-service + run: go test ./test/... + check-cluster: name: Check if GKE cluster exists runs-on: ubuntu-latest @@ -45,7 +86,7 @@ jobs: build-publish-deploy: name: Build, Publish, and Deploy - needs: check-cluster + needs: [test, check-cluster] if: needs.check-cluster.outputs.exists == 'true' runs-on: ubuntu-latest environment: production From 773c871b55009ad5ef5291dee355965a2f680620 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 28 May 2025 11:31:30 +0100 Subject: [PATCH 2/7] Add Unit Tests --- code/services/comment-service/go.mod | 6 + code/services/comment-service/go.sum | 18 ++ .../comment-service/test/server_test.go | 163 ++++++++++++ code/services/community-service/go.mod | 4 + code/services/community-service/go.sum | 5 + .../community-service/test/server_test.go | 161 ++++++++++++ code/services/popular-service/go.mod | 4 + code/services/popular-service/go.sum | 5 + .../popular-service/test/server_test.go | 158 ++++++++++++ code/services/search-service/go.mod | 4 + code/services/search-service/go.sum | 5 + .../search-service/test/server_test.go | 223 +++++++++++++++++ code/services/thread-service/go.mod | 1 + code/services/thread-service/go.sum | 29 +++ .../thread-service/test/server_test.go | 236 ++++++++++++++++++ code/services/vote-service/go.mod | 4 + code/services/vote-service/go.sum | 5 + .../services/vote-service/test/server_test.go | 116 +++++++++ 18 files changed, 1147 insertions(+) create mode 100644 code/services/comment-service/test/server_test.go create mode 100644 code/services/community-service/test/server_test.go create mode 100644 code/services/popular-service/test/server_test.go create mode 100644 code/services/search-service/test/server_test.go create mode 100644 code/services/thread-service/test/server_test.go create mode 100644 code/services/vote-service/test/server_test.go diff --git a/code/services/comment-service/go.mod b/code/services/comment-service/go.mod index b5d1d94..d30e932 100644 --- a/code/services/comment-service/go.mod +++ b/code/services/comment-service/go.mod @@ -6,17 +6,23 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/comment-service/go.sum b/code/services/comment-service/go.sum index 15c472b..18f075a 100644 --- a/code/services/comment-service/go.sum +++ b/code/services/comment-service/go.sum @@ -1,3 +1,6 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,6 +13,16 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -36,3 +49,8 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/comment-service/test/server_test.go b/code/services/comment-service/test/server_test.go new file mode 100644 index 0000000..7b4ed4d --- /dev/null +++ b/code/services/comment-service/test/server_test.go @@ -0,0 +1,163 @@ +package test + +import ( + "context" + "testing" + + src "comment-service/src" + commentpb "gen/comment-service/pb" + dbpb "gen/db-service/pb" + models "gen/models/pb" + threadpb "gen/thread-service/pb" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type MockDBClient struct { + dbpb.DBServiceClient + ListCommentsFunc func(ctx context.Context, req *dbpb.ListCommentsRequest) (*dbpb.ListCommentsResponse, error) + CreateCommentFunc func(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) + GetCommentFunc func(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) + UpdateCommentFunc func(ctx context.Context, req *dbpb.UpdateCommentRequest) (*emptypb.Empty, error) + DeleteCommentFunc func(ctx context.Context, req *dbpb.DeleteCommentRequest) (*emptypb.Empty, error) +} + +func (m *MockDBClient) ListComments(ctx context.Context, req *dbpb.ListCommentsRequest) (*dbpb.ListCommentsResponse, error) { + return m.ListCommentsFunc(ctx, req) +} + +func (m *MockDBClient) CreateComment(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) { + return m.CreateCommentFunc(ctx, req) +} + +func (m *MockDBClient) GetComment(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) { + return m.GetCommentFunc(ctx, req) +} + +func (m *MockDBClient) UpdateComment(ctx context.Context, req *dbpb.UpdateCommentRequest) (*emptypb.Empty, error) { + return m.UpdateCommentFunc(ctx, req) +} + +func (m *MockDBClient) DeleteComment(ctx context.Context, req *dbpb.DeleteCommentRequest) (*emptypb.Empty, error) { + return m.DeleteCommentFunc(ctx, req) +} + +type MockThreadClient struct { + threadpb.ThreadServiceClient + UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) +} + +func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req) +} + +func TestCreateComment_Validation(t *testing.T) { + tests := []struct { + name string + req *commentpb.CreateCommentRequest + wantErr error + }{ + { + name: "missing parent id", + req: &commentpb.CreateCommentRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Parent id is required"), + }, + { + name: "missing content", + req: &commentpb.CreateCommentRequest{ + ParentId: "123", + }, + wantErr: status.Error(codes.InvalidArgument, "Content is required"), + }, + { + name: "content too long", + req: &commentpb.CreateCommentRequest{ + ParentId: "123", + Content: string(make([]byte, 501)), + }, + wantErr: status.Error(codes.InvalidArgument, "Content exceeds maximum length of 500 characters"), + }, + { + name: "valid request", + req: &commentpb.CreateCommentRequest{ + ParentId: "123", + Content: "test comment", + ParentType: models.CommentParentType_THREAD, + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.CommentServer{ + DBClient: &MockDBClient{ + CreateCommentFunc: func(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) { + return &dbpb.CreateCommentResponse{ + Id: "123", + }, nil + }, + }, + ThreadClient: &MockThreadClient{ + UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil + }, + }, + } + + _, err := server.CreateComment(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetComment_Validation(t *testing.T) { + tests := []struct { + name string + req *commentpb.GetCommentRequest + wantErr error + }{ + { + name: "missing id", + req: &commentpb.GetCommentRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Comment id is required"), + }, + { + name: "valid request", + req: &commentpb.GetCommentRequest{ + Id: "123", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.CommentServer{ + DBClient: &MockDBClient{ + GetCommentFunc: func(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) { + return &models.Comment{ + Id: "123", + Content: "test comment", + }, nil + }, + }, + ThreadClient: &MockThreadClient{}, + } + + _, err := server.GetComment(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/code/services/community-service/go.mod b/code/services/community-service/go.mod index e441ff1..8b0ddbf 100644 --- a/code/services/community-service/go.mod +++ b/code/services/community-service/go.mod @@ -6,17 +6,21 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/community-service/go.sum b/code/services/community-service/go.sum index 15c472b..cd6e1f2 100644 --- a/code/services/community-service/go.sum +++ b/code/services/community-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,6 +11,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -36,3 +39,5 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/community-service/test/server_test.go b/code/services/community-service/test/server_test.go new file mode 100644 index 0000000..61f400e --- /dev/null +++ b/code/services/community-service/test/server_test.go @@ -0,0 +1,161 @@ +package test + +import ( + "context" + "testing" + + src "community-service/src" + communitypb "gen/community-service/pb" + dbpb "gen/db-service/pb" + models "gen/models/pb" + threadpb "gen/thread-service/pb" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type MockDBClient struct { + dbpb.DBServiceClient + ListCommunitiesFunc func(ctx context.Context, req *dbpb.ListCommunitiesRequest) (*dbpb.ListCommunitiesResponse, error) + CreateCommunityFunc func(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) + GetCommunityFunc func(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) + UpdateCommunityFunc func(ctx context.Context, req *dbpb.UpdateCommunityRequest) (*emptypb.Empty, error) + DeleteCommunityFunc func(ctx context.Context, req *dbpb.DeleteCommunityRequest) (*emptypb.Empty, error) +} + +func (m *MockDBClient) ListCommunities(ctx context.Context, req *dbpb.ListCommunitiesRequest) (*dbpb.ListCommunitiesResponse, error) { + return m.ListCommunitiesFunc(ctx, req) +} + +func (m *MockDBClient) CreateCommunity(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) { + return m.CreateCommunityFunc(ctx, req) +} + +func (m *MockDBClient) GetCommunity(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) { + return m.GetCommunityFunc(ctx, req) +} + +func (m *MockDBClient) UpdateCommunity(ctx context.Context, req *dbpb.UpdateCommunityRequest) (*emptypb.Empty, error) { + return m.UpdateCommunityFunc(ctx, req) +} + +func (m *MockDBClient) DeleteCommunity(ctx context.Context, req *dbpb.DeleteCommunityRequest) (*emptypb.Empty, error) { + return m.DeleteCommunityFunc(ctx, req) +} + +type MockThreadClient struct { + threadpb.ThreadServiceClient + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) + DeleteThreadFunc func(ctx context.Context, req *threadpb.DeleteThreadRequest) (*emptypb.Empty, error) +} + +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req) +} + +func (m *MockThreadClient) DeleteThread(ctx context.Context, req *threadpb.DeleteThreadRequest) (*emptypb.Empty, error) { + return m.DeleteThreadFunc(ctx, req) +} + +func TestCreateCommunity_Validation(t *testing.T) { + tests := []struct { + name string + req *communitypb.CreateCommunityRequest + wantErr error + }{ + { + name: "missing name", + req: &communitypb.CreateCommunityRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Community name is required"), + }, + { + name: "name too short", + req: &communitypb.CreateCommunityRequest{ + Name: "ab", + }, + wantErr: status.Error(codes.InvalidArgument, "Name must be between 3 and 50 characters long"), + }, + { + name: "name too long", + req: &communitypb.CreateCommunityRequest{ + Name: "a", + }, + wantErr: status.Error(codes.InvalidArgument, "Name must be between 3 and 50 characters long"), + }, + { + name: "valid request", + req: &communitypb.CreateCommunityRequest{ + Name: "test-community", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.CommunityServer{ + DBClient: &MockDBClient{ + CreateCommunityFunc: func(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) { + return &dbpb.CreateCommunityResponse{ + Id: "123", + }, nil + }, + }, + ThreadClient: &MockThreadClient{}, + } + + _, err := server.CreateCommunity(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetCommunity_Validation(t *testing.T) { + tests := []struct { + name string + req *communitypb.GetCommunityRequest + wantErr error + }{ + { + name: "missing id", + req: &communitypb.GetCommunityRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Community id is required"), + }, + { + name: "valid request", + req: &communitypb.GetCommunityRequest{ + Id: "123", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.CommunityServer{ + DBClient: &MockDBClient{ + GetCommunityFunc: func(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) { + return &models.Community{ + Id: "123", + Name: "test-community", + }, nil + }, + }, + ThreadClient: &MockThreadClient{}, + } + + _, err := server.GetCommunity(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/code/services/popular-service/go.mod b/code/services/popular-service/go.mod index df21999..176bc81 100644 --- a/code/services/popular-service/go.mod +++ b/code/services/popular-service/go.mod @@ -6,17 +6,21 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.71.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/popular-service/go.sum b/code/services/popular-service/go.sum index 15c472b..cd6e1f2 100644 --- a/code/services/popular-service/go.sum +++ b/code/services/popular-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,6 +11,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -36,3 +39,5 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/popular-service/test/server_test.go b/code/services/popular-service/test/server_test.go new file mode 100644 index 0000000..a2bd02d --- /dev/null +++ b/code/services/popular-service/test/server_test.go @@ -0,0 +1,158 @@ +package test + +import ( + "context" + "testing" + + commentpb "gen/comment-service/pb" + models "gen/models/pb" + popularpb "gen/popular-service/pb" + threadpb "gen/thread-service/pb" + src "popular-service/src" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type MockThreadClient struct { + threadpb.ThreadServiceClient + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) +} + +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req) +} + +type MockCommentClient struct { + commentpb.CommentServiceClient + ListCommentsFunc func(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) +} + +func (m *MockCommentClient) ListComments(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) { + return m.ListCommentsFunc(ctx, req) +} + +func TestGetPopularThreads_Validation(t *testing.T) { + tests := []struct { + name string + req *popularpb.GetPopularThreadsRequest + wantErr error + }{ + { + name: "negative offset", + req: &popularpb.GetPopularThreadsRequest{ + Offset: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Offset must be a positive integer"), + }, + { + name: "zero limit", + req: &popularpb.GetPopularThreadsRequest{ + Limit: int32Ptr(0), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit must be a positive integer"), + }, + { + name: "negative limit", + req: &popularpb.GetPopularThreadsRequest{ + Limit: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit must be a positive integer"), + }, + { + name: "valid request", + req: &popularpb.GetPopularThreadsRequest{ + Offset: int32Ptr(0), + Limit: int32Ptr(10), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.PopularServer{ + ThreadClient: &MockThreadClient{ + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return &threadpb.ListThreadsResponse{ + Threads: []*models.Thread{}, + }, nil + }, + }, + CommentClient: &MockCommentClient{}, + } + + _, err := server.GetPopularThreads(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetPopularComments_Validation(t *testing.T) { + tests := []struct { + name string + req *popularpb.GetPopularCommentsRequest + wantErr error + }{ + { + name: "negative offset", + req: &popularpb.GetPopularCommentsRequest{ + Offset: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Offset must be a positive integer"), + }, + { + name: "zero limit", + req: &popularpb.GetPopularCommentsRequest{ + Limit: int32Ptr(0), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit must be a positive integer"), + }, + { + name: "negative limit", + req: &popularpb.GetPopularCommentsRequest{ + Limit: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit must be a positive integer"), + }, + { + name: "valid request", + req: &popularpb.GetPopularCommentsRequest{ + Offset: int32Ptr(0), + Limit: int32Ptr(10), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.PopularServer{ + ThreadClient: &MockThreadClient{}, + CommentClient: &MockCommentClient{ + ListCommentsFunc: func(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) { + return &commentpb.ListCommentsResponse{ + Comments: []*models.Comment{}, + }, nil + }, + }, + } + + _, err := server.GetPopularComments(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func int32Ptr(i int32) *int32 { + return &i +} diff --git a/code/services/search-service/go.mod b/code/services/search-service/go.mod index b799d82..ee14ed8 100644 --- a/code/services/search-service/go.mod +++ b/code/services/search-service/go.mod @@ -6,17 +6,21 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.71.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/search-service/go.sum b/code/services/search-service/go.sum index 15c472b..cd6e1f2 100644 --- a/code/services/search-service/go.sum +++ b/code/services/search-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,6 +11,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -36,3 +39,5 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/search-service/test/server_test.go b/code/services/search-service/test/server_test.go new file mode 100644 index 0000000..4883d35 --- /dev/null +++ b/code/services/search-service/test/server_test.go @@ -0,0 +1,223 @@ +package test + +import ( + "context" + "testing" + + communitypb "gen/community-service/pb" + models "gen/models/pb" + searchpb "gen/search-service/pb" + threadpb "gen/thread-service/pb" + src "search-service/src" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type MockCommunityClient struct { + communitypb.CommunityServiceClient + ListCommunitiesFunc func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) +} + +func (m *MockCommunityClient) ListCommunities(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { + return m.ListCommunitiesFunc(ctx, req) +} + +type MockThreadClient struct { + threadpb.ThreadServiceClient + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) +} + +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req) +} + +func TestGlobalSearch_Validation(t *testing.T) { + tests := []struct { + name string + req *searchpb.SearchRequest + wantErr error + }{ + { + name: "empty query", + req: &searchpb.SearchRequest{Query: ""}, + wantErr: status.Error(codes.InvalidArgument, "Query is empty"), + }, + { + name: "negative offset", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Offset cannot be negative"), + }, + { + name: "negative limit", + req: &searchpb.SearchRequest{ + Query: "test", + Limit: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit cannot be negative"), + }, + { + name: "valid request", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(0), + Limit: int32Ptr(10), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.SearchServer{ + CommunityClient: &MockCommunityClient{ + ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { + return &communitypb.ListCommunitiesResponse{ + Communities: []*models.Community{}, + }, nil + }, + }, + ThreadClient: &MockThreadClient{ + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return &threadpb.ListThreadsResponse{ + Threads: []*models.Thread{}, + }, nil + }, + }, + } + + _, err := server.GlobalSearch(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestCommunitySearch_Validation(t *testing.T) { + tests := []struct { + name string + req *searchpb.SearchRequest + wantErr error + }{ + { + name: "empty query", + req: &searchpb.SearchRequest{Query: ""}, + wantErr: status.Error(codes.InvalidArgument, "Query is empty"), + }, + { + name: "negative offset", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Offset cannot be negative"), + }, + { + name: "negative limit", + req: &searchpb.SearchRequest{ + Query: "test", + Limit: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit cannot be negative"), + }, + { + name: "valid request", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(0), + Limit: int32Ptr(10), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.SearchServer{ + CommunityClient: &MockCommunityClient{ + ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { + return &communitypb.ListCommunitiesResponse{ + Communities: []*models.Community{}, + }, nil + }, + }, + } + + _, err := server.CommunitySearch(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestThreadSearch_Validation(t *testing.T) { + tests := []struct { + name string + req *searchpb.SearchRequest + wantErr error + }{ + { + name: "empty query", + req: &searchpb.SearchRequest{Query: ""}, + wantErr: status.Error(codes.InvalidArgument, "Query is empty"), + }, + { + name: "negative offset", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Offset cannot be negative"), + }, + { + name: "negative limit", + req: &searchpb.SearchRequest{ + Query: "test", + Limit: int32Ptr(-1), + }, + wantErr: status.Error(codes.InvalidArgument, "Limit cannot be negative"), + }, + { + name: "valid request", + req: &searchpb.SearchRequest{ + Query: "test", + Offset: int32Ptr(0), + Limit: int32Ptr(10), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.SearchServer{ + ThreadClient: &MockThreadClient{ + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + return &threadpb.ListThreadsResponse{ + Threads: []*models.Thread{}, + }, nil + }, + }, + } + + _, err := server.ThreadSearch(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func int32Ptr(i int32) *int32 { return &i } diff --git a/code/services/thread-service/go.mod b/code/services/thread-service/go.mod index 5adaafc..6cb5ac3 100644 --- a/code/services/thread-service/go.mod +++ b/code/services/thread-service/go.mod @@ -12,6 +12,7 @@ require ( require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/stretchr/testify v1.10.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect diff --git a/code/services/thread-service/go.sum b/code/services/thread-service/go.sum index 15c472b..7c94794 100644 --- a/code/services/thread-service/go.sum +++ b/code/services/thread-service/go.sum @@ -1,7 +1,19 @@ +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -10,8 +22,16 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= @@ -22,12 +42,19 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= @@ -36,3 +63,5 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/thread-service/test/server_test.go b/code/services/thread-service/test/server_test.go new file mode 100644 index 0000000..6545926 --- /dev/null +++ b/code/services/thread-service/test/server_test.go @@ -0,0 +1,236 @@ +package test + +import ( + "context" + "testing" + + communitypb "gen/community-service/pb" + dbpb "gen/db-service/pb" + models "gen/models/pb" + threadpb "gen/thread-service/pb" + src "thread-service/src" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type MockDBClient struct { + dbpb.DBServiceClient + ListThreadsFunc func(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) + CreateThreadFunc func(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) + GetThreadFunc func(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) + UpdateThreadFunc func(ctx context.Context, req *dbpb.UpdateThreadRequest) (*emptypb.Empty, error) + DeleteThreadFunc func(ctx context.Context, req *dbpb.DeleteThreadRequest) (*emptypb.Empty, error) +} + +func (m *MockDBClient) ListThreads(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req) +} + +func (m *MockDBClient) CreateThread(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) { + return m.CreateThreadFunc(ctx, req) +} + +func (m *MockDBClient) GetThread(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) { + return m.GetThreadFunc(ctx, req) +} + +func (m *MockDBClient) UpdateThread(ctx context.Context, req *dbpb.UpdateThreadRequest) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req) +} + +func (m *MockDBClient) DeleteThread(ctx context.Context, req *dbpb.DeleteThreadRequest) (*emptypb.Empty, error) { + return m.DeleteThreadFunc(ctx, req) +} + +type MockCommunityClient struct { + communitypb.CommunityServiceClient + GetCommunityFunc func(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) +} + +func (m *MockCommunityClient) GetCommunity(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) { + return m.GetCommunityFunc(ctx, req) +} + +func TestCreateThread_Validation(t *testing.T) { + tests := []struct { + name string + req *threadpb.CreateThreadRequest + wantErr error + }{ + { + name: "missing community id", + req: &threadpb.CreateThreadRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Community id is required"), + }, + { + name: "missing title", + req: &threadpb.CreateThreadRequest{ + CommunityId: "123", + }, + wantErr: status.Error(codes.InvalidArgument, "Title is required"), + }, + { + name: "missing content", + req: &threadpb.CreateThreadRequest{ + CommunityId: "123", + Title: "test thread", + }, + wantErr: status.Error(codes.InvalidArgument, "Content is required"), + }, + { + name: "valid request", + req: &threadpb.CreateThreadRequest{ + CommunityId: "123", + Title: "test thread", + Content: "test content", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.ThreadServer{ + DBClient: &MockDBClient{ + CreateThreadFunc: func(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) { + return &dbpb.CreateThreadResponse{ + Id: "123", + }, nil + }, + }, + } + + _, err := server.CreateThread(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestGetThread_Validation(t *testing.T) { + tests := []struct { + name string + req *threadpb.GetThreadRequest + wantErr error + }{ + { + name: "missing id", + req: &threadpb.GetThreadRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Thread id is required"), + }, + { + name: "valid request", + req: &threadpb.GetThreadRequest{ + Id: "123", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.ThreadServer{ + DBClient: &MockDBClient{ + GetThreadFunc: func(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) { + return &models.Thread{ + Id: "123", + CommunityId: "456", + Title: "test thread", + Content: "test content", + }, nil + }, + }, + } + + _, err := server.GetThread(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestListThreads_Validation(t *testing.T) { + tests := []struct { + name string + req *threadpb.ListThreadsRequest + wantErr error + }{ + { + name: "empty community id", + req: &threadpb.ListThreadsRequest{CommunityId: strPtr("")}, + wantErr: status.Error(codes.InvalidArgument, "Community id cannot be empty"), + }, + { + name: "empty title", + req: &threadpb.ListThreadsRequest{Title: strPtr("")}, + wantErr: status.Error(codes.InvalidArgument, "Title cannot be empty"), + }, + { + name: "negative offset", + req: &threadpb.ListThreadsRequest{Offset: int32Ptr(-1)}, + wantErr: status.Error(codes.InvalidArgument, "Offset cannot be negative"), + }, + { + name: "zero limit", + req: &threadpb.ListThreadsRequest{Limit: int32Ptr(0)}, + wantErr: status.Error(codes.InvalidArgument, "Limit must be positive"), + }, + { + name: "empty sort", + req: &threadpb.ListThreadsRequest{SortBy: strPtr("")}, + wantErr: status.Error(codes.InvalidArgument, "Sort by cannot be empty"), + }, + { + name: "valid request", + req: &threadpb.ListThreadsRequest{ + CommunityId: strPtr("123"), + Title: strPtr("test"), + Offset: int32Ptr(0), + Limit: int32Ptr(10), + SortBy: strPtr("created_at"), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.ThreadServer{ + DBClient: &MockDBClient{ + ListThreadsFunc: func(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) { + return &dbpb.ListThreadsResponse{ + Threads: []*models.Thread{}, + }, nil + }, + }, + CommunityClient: &MockCommunityClient{ + GetCommunityFunc: func(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) { + return &models.Community{ + Id: "123", + Name: "test-community", + }, nil + }, + }, + } + + _, err := server.ListThreads(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func strPtr(s string) *string { return &s } +func int32Ptr(i int32) *int32 { return &i } diff --git a/code/services/vote-service/go.mod b/code/services/vote-service/go.mod index 85a42c0..15a034a 100644 --- a/code/services/vote-service/go.mod +++ b/code/services/vote-service/go.mod @@ -6,17 +6,21 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/vote-service/go.sum b/code/services/vote-service/go.sum index 15c472b..cd6e1f2 100644 --- a/code/services/vote-service/go.sum +++ b/code/services/vote-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -10,6 +11,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -36,3 +39,5 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/vote-service/test/server_test.go b/code/services/vote-service/test/server_test.go new file mode 100644 index 0000000..bb17d60 --- /dev/null +++ b/code/services/vote-service/test/server_test.go @@ -0,0 +1,116 @@ +package test + +import ( + "context" + "testing" + + commentpb "gen/comment-service/pb" + threadpb "gen/thread-service/pb" + votepb "gen/vote-service/pb" + src "vote-service/src" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" +) + +type MockThreadClient struct { + threadpb.ThreadServiceClient + UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) +} + +func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req) +} + +type MockCommentClient struct { + commentpb.CommentServiceClient + UpdateCommentFunc func(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) +} + +func (m *MockCommentClient) UpdateComment(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) { + return m.UpdateCommentFunc(ctx, req) +} + +func TestUpvoteThread_Validation(t *testing.T) { + tests := []struct { + name string + req *votepb.VoteThreadRequest + wantErr error + }{ + { + name: "missing thread id", + req: &votepb.VoteThreadRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Thread id is required"), + }, + { + name: "valid request", + req: &votepb.VoteThreadRequest{ + ThreadId: "123", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.VoteServer{ + ThreadClient: &MockThreadClient{ + UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil + }, + }, + CommentClient: &MockCommentClient{}, + } + + _, err := server.UpvoteThread(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUpvoteComment_Validation(t *testing.T) { + tests := []struct { + name string + req *votepb.VoteCommentRequest + wantErr error + }{ + { + name: "missing comment id", + req: &votepb.VoteCommentRequest{}, + wantErr: status.Error(codes.InvalidArgument, "Comment id is required"), + }, + { + name: "valid request", + req: &votepb.VoteCommentRequest{ + CommentId: "123", + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := &src.VoteServer{ + ThreadClient: &MockThreadClient{}, + CommentClient: &MockCommentClient{ + UpdateCommentFunc: func(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil + }, + }, + } + + _, err := server.UpvoteComment(context.Background(), tt.req) + if tt.wantErr != nil { + assert.Equal(t, tt.wantErr.Error(), err.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} From db93943ac1ad9c0993bd0475e3a74b5084073975 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 28 May 2025 12:12:57 +0100 Subject: [PATCH 3/7] Fix Unit Tests --- .../comment-service/test/server_test.go | 43 +++++----- code/services/community-service/go.mod | 2 +- code/services/community-service/go.sum | 5 ++ .../community-service/test/server_test.go | 47 +++++------ code/services/popular-service/go.mod | 2 +- code/services/popular-service/go.sum | 5 ++ .../popular-service/test/server_test.go | 17 ++-- code/services/run_tests.sh | 28 +++++++ code/services/search-service/go.mod | 2 +- code/services/search-service/go.sum | 5 ++ .../search-service/test/server_test.go | 21 ++--- code/services/thread-service/go.mod | 3 + code/services/thread-service/go.sum | 4 + .../thread-service/test/server_test.go | 82 +++++++++++-------- code/services/vote-service/go.mod | 4 +- code/services/vote-service/go.sum | 15 ++++ .../services/vote-service/test/server_test.go | 23 ++++-- 17 files changed, 199 insertions(+), 109 deletions(-) create mode 100644 code/services/run_tests.sh diff --git a/code/services/comment-service/test/server_test.go b/code/services/comment-service/test/server_test.go index 7b4ed4d..ffa31ec 100644 --- a/code/services/comment-service/test/server_test.go +++ b/code/services/comment-service/test/server_test.go @@ -11,6 +11,7 @@ import ( threadpb "gen/thread-service/pb" "github.com/stretchr/testify/assert" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" @@ -18,40 +19,40 @@ import ( type MockDBClient struct { dbpb.DBServiceClient - ListCommentsFunc func(ctx context.Context, req *dbpb.ListCommentsRequest) (*dbpb.ListCommentsResponse, error) - CreateCommentFunc func(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) - GetCommentFunc func(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) - UpdateCommentFunc func(ctx context.Context, req *dbpb.UpdateCommentRequest) (*emptypb.Empty, error) - DeleteCommentFunc func(ctx context.Context, req *dbpb.DeleteCommentRequest) (*emptypb.Empty, error) + ListCommentsFunc func(ctx context.Context, req *dbpb.ListCommentsRequest, opts ...grpc.CallOption) (*dbpb.ListCommentsResponse, error) + CreateCommentFunc func(ctx context.Context, req *dbpb.CreateCommentRequest, opts ...grpc.CallOption) (*dbpb.CreateCommentResponse, error) + GetCommentFunc func(ctx context.Context, req *dbpb.GetCommentRequest, opts ...grpc.CallOption) (*models.Comment, error) + UpdateCommentFunc func(ctx context.Context, req *dbpb.UpdateCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeleteCommentFunc func(ctx context.Context, req *dbpb.DeleteCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockDBClient) ListComments(ctx context.Context, req *dbpb.ListCommentsRequest) (*dbpb.ListCommentsResponse, error) { - return m.ListCommentsFunc(ctx, req) +func (m *MockDBClient) ListComments(ctx context.Context, req *dbpb.ListCommentsRequest, opts ...grpc.CallOption) (*dbpb.ListCommentsResponse, error) { + return m.ListCommentsFunc(ctx, req, opts...) } -func (m *MockDBClient) CreateComment(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) { - return m.CreateCommentFunc(ctx, req) +func (m *MockDBClient) CreateComment(ctx context.Context, req *dbpb.CreateCommentRequest, opts ...grpc.CallOption) (*dbpb.CreateCommentResponse, error) { + return m.CreateCommentFunc(ctx, req, opts...) } -func (m *MockDBClient) GetComment(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) { - return m.GetCommentFunc(ctx, req) +func (m *MockDBClient) GetComment(ctx context.Context, req *dbpb.GetCommentRequest, opts ...grpc.CallOption) (*models.Comment, error) { + return m.GetCommentFunc(ctx, req, opts...) } -func (m *MockDBClient) UpdateComment(ctx context.Context, req *dbpb.UpdateCommentRequest) (*emptypb.Empty, error) { - return m.UpdateCommentFunc(ctx, req) +func (m *MockDBClient) UpdateComment(ctx context.Context, req *dbpb.UpdateCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateCommentFunc(ctx, req, opts...) } -func (m *MockDBClient) DeleteComment(ctx context.Context, req *dbpb.DeleteCommentRequest) (*emptypb.Empty, error) { - return m.DeleteCommentFunc(ctx, req) +func (m *MockDBClient) DeleteComment(ctx context.Context, req *dbpb.DeleteCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.DeleteCommentFunc(ctx, req, opts...) } type MockThreadClient struct { threadpb.ThreadServiceClient - UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) + UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { - return m.UpdateThreadFunc(ctx, req) +func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req, opts...) } func TestCreateComment_Validation(t *testing.T) { @@ -95,14 +96,14 @@ func TestCreateComment_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.CommentServer{ DBClient: &MockDBClient{ - CreateCommentFunc: func(ctx context.Context, req *dbpb.CreateCommentRequest) (*dbpb.CreateCommentResponse, error) { + CreateCommentFunc: func(ctx context.Context, req *dbpb.CreateCommentRequest, opts ...grpc.CallOption) (*dbpb.CreateCommentResponse, error) { return &dbpb.CreateCommentResponse{ Id: "123", }, nil }, }, ThreadClient: &MockThreadClient{ - UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { return &emptypb.Empty{}, nil }, }, @@ -142,7 +143,7 @@ func TestGetComment_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.CommentServer{ DBClient: &MockDBClient{ - GetCommentFunc: func(ctx context.Context, req *dbpb.GetCommentRequest) (*models.Comment, error) { + GetCommentFunc: func(ctx context.Context, req *dbpb.GetCommentRequest, opts ...grpc.CallOption) (*models.Comment, error) { return &models.Comment{ Id: "123", Content: "test comment", diff --git a/code/services/community-service/go.mod b/code/services/community-service/go.mod index 8b0ddbf..02a3b92 100644 --- a/code/services/community-service/go.mod +++ b/code/services/community-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) diff --git a/code/services/community-service/go.sum b/code/services/community-service/go.sum index cd6e1f2..709e1e0 100644 --- a/code/services/community-service/go.sum +++ b/code/services/community-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -11,8 +12,11 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -40,4 +44,5 @@ google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/community-service/test/server_test.go b/code/services/community-service/test/server_test.go index 61f400e..9819e04 100644 --- a/code/services/community-service/test/server_test.go +++ b/code/services/community-service/test/server_test.go @@ -14,49 +14,50 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/grpc" ) type MockDBClient struct { dbpb.DBServiceClient - ListCommunitiesFunc func(ctx context.Context, req *dbpb.ListCommunitiesRequest) (*dbpb.ListCommunitiesResponse, error) - CreateCommunityFunc func(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) - GetCommunityFunc func(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) - UpdateCommunityFunc func(ctx context.Context, req *dbpb.UpdateCommunityRequest) (*emptypb.Empty, error) - DeleteCommunityFunc func(ctx context.Context, req *dbpb.DeleteCommunityRequest) (*emptypb.Empty, error) + ListCommunitiesFunc func(ctx context.Context, req *dbpb.ListCommunitiesRequest, opts ...grpc.CallOption) (*dbpb.ListCommunitiesResponse, error) + CreateCommunityFunc func(ctx context.Context, req *dbpb.CreateCommunityRequest, opts ...grpc.CallOption) (*dbpb.CreateCommunityResponse, error) + GetCommunityFunc func(ctx context.Context, req *dbpb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) + UpdateCommunityFunc func(ctx context.Context, req *dbpb.UpdateCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeleteCommunityFunc func(ctx context.Context, req *dbpb.DeleteCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockDBClient) ListCommunities(ctx context.Context, req *dbpb.ListCommunitiesRequest) (*dbpb.ListCommunitiesResponse, error) { - return m.ListCommunitiesFunc(ctx, req) +func (m *MockDBClient) ListCommunities(ctx context.Context, req *dbpb.ListCommunitiesRequest, opts ...grpc.CallOption) (*dbpb.ListCommunitiesResponse, error) { + return m.ListCommunitiesFunc(ctx, req, opts...) } -func (m *MockDBClient) CreateCommunity(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) { - return m.CreateCommunityFunc(ctx, req) +func (m *MockDBClient) CreateCommunity(ctx context.Context, req *dbpb.CreateCommunityRequest, opts ...grpc.CallOption) (*dbpb.CreateCommunityResponse, error) { + return m.CreateCommunityFunc(ctx, req, opts...) } -func (m *MockDBClient) GetCommunity(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) { - return m.GetCommunityFunc(ctx, req) +func (m *MockDBClient) GetCommunity(ctx context.Context, req *dbpb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) { + return m.GetCommunityFunc(ctx, req, opts...) } -func (m *MockDBClient) UpdateCommunity(ctx context.Context, req *dbpb.UpdateCommunityRequest) (*emptypb.Empty, error) { - return m.UpdateCommunityFunc(ctx, req) +func (m *MockDBClient) UpdateCommunity(ctx context.Context, req *dbpb.UpdateCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateCommunityFunc(ctx, req, opts...) } -func (m *MockDBClient) DeleteCommunity(ctx context.Context, req *dbpb.DeleteCommunityRequest) (*emptypb.Empty, error) { - return m.DeleteCommunityFunc(ctx, req) +func (m *MockDBClient) DeleteCommunity(ctx context.Context, req *dbpb.DeleteCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.DeleteCommunityFunc(ctx, req, opts...) } type MockThreadClient struct { threadpb.ThreadServiceClient - ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) - DeleteThreadFunc func(ctx context.Context, req *threadpb.DeleteThreadRequest) (*emptypb.Empty, error) + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) + DeleteThreadFunc func(ctx context.Context, req *threadpb.DeleteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { - return m.ListThreadsFunc(ctx, req) +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req, opts...) } -func (m *MockThreadClient) DeleteThread(ctx context.Context, req *threadpb.DeleteThreadRequest) (*emptypb.Empty, error) { - return m.DeleteThreadFunc(ctx, req) +func (m *MockThreadClient) DeleteThread(ctx context.Context, req *threadpb.DeleteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.DeleteThreadFunc(ctx, req, opts...) } func TestCreateCommunity_Validation(t *testing.T) { @@ -97,7 +98,7 @@ func TestCreateCommunity_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.CommunityServer{ DBClient: &MockDBClient{ - CreateCommunityFunc: func(ctx context.Context, req *dbpb.CreateCommunityRequest) (*dbpb.CreateCommunityResponse, error) { + CreateCommunityFunc: func(ctx context.Context, req *dbpb.CreateCommunityRequest, opts ...grpc.CallOption) (*dbpb.CreateCommunityResponse, error) { return &dbpb.CreateCommunityResponse{ Id: "123", }, nil @@ -140,7 +141,7 @@ func TestGetCommunity_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.CommunityServer{ DBClient: &MockDBClient{ - GetCommunityFunc: func(ctx context.Context, req *dbpb.GetCommunityRequest) (*models.Community, error) { + GetCommunityFunc: func(ctx context.Context, req *dbpb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) { return &models.Community{ Id: "123", Name: "test-community", diff --git a/code/services/popular-service/go.mod b/code/services/popular-service/go.mod index 176bc81..6ddef57 100644 --- a/code/services/popular-service/go.mod +++ b/code/services/popular-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.0 ) diff --git a/code/services/popular-service/go.sum b/code/services/popular-service/go.sum index cd6e1f2..709e1e0 100644 --- a/code/services/popular-service/go.sum +++ b/code/services/popular-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -11,8 +12,11 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -40,4 +44,5 @@ google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/popular-service/test/server_test.go b/code/services/popular-service/test/server_test.go index a2bd02d..2adbf95 100644 --- a/code/services/popular-service/test/server_test.go +++ b/code/services/popular-service/test/server_test.go @@ -13,24 +13,25 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/grpc" ) type MockThreadClient struct { threadpb.ThreadServiceClient - ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) } -func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { - return m.ListThreadsFunc(ctx, req) +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req, opts...) } type MockCommentClient struct { commentpb.CommentServiceClient - ListCommentsFunc func(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) + ListCommentsFunc func(ctx context.Context, req *commentpb.ListCommentsRequest, opts ...grpc.CallOption) (*commentpb.ListCommentsResponse, error) } -func (m *MockCommentClient) ListComments(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) { - return m.ListCommentsFunc(ctx, req) +func (m *MockCommentClient) ListComments(ctx context.Context, req *commentpb.ListCommentsRequest, opts ...grpc.CallOption) (*commentpb.ListCommentsResponse, error) { + return m.ListCommentsFunc(ctx, req, opts...) } func TestGetPopularThreads_Validation(t *testing.T) { @@ -74,7 +75,7 @@ func TestGetPopularThreads_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.PopularServer{ ThreadClient: &MockThreadClient{ - ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { return &threadpb.ListThreadsResponse{ Threads: []*models.Thread{}, }, nil @@ -135,7 +136,7 @@ func TestGetPopularComments_Validation(t *testing.T) { server := &src.PopularServer{ ThreadClient: &MockThreadClient{}, CommentClient: &MockCommentClient{ - ListCommentsFunc: func(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) { + ListCommentsFunc: func(ctx context.Context, req *commentpb.ListCommentsRequest, opts ...grpc.CallOption) (*commentpb.ListCommentsResponse, error) { return &commentpb.ListCommentsResponse{ Comments: []*models.Comment{}, }, nil diff --git a/code/services/run_tests.sh b/code/services/run_tests.sh new file mode 100644 index 0000000..dac00b7 --- /dev/null +++ b/code/services/run_tests.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +services=( + "vote-service" + "thread-service" + "search-service" + "popular-service" + "community-service" + "comment-service" +) + +for service in "${services[@]}"; do + echo "Running tests for $service..." + cd "$service/test" || { + echo "Failed to change directory to $service/test" + continue + } + + # Run the tests + go test -v ./... + + # Return to the services directory + cd ../.. + + echo "----------------------------------------" +done + +echo "All tests completed!" \ No newline at end of file diff --git a/code/services/search-service/go.mod b/code/services/search-service/go.mod index ee14ed8..fdde518 100644 --- a/code/services/search-service/go.mod +++ b/code/services/search-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.0 ) diff --git a/code/services/search-service/go.sum b/code/services/search-service/go.sum index cd6e1f2..709e1e0 100644 --- a/code/services/search-service/go.sum +++ b/code/services/search-service/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -11,8 +12,11 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -40,4 +44,5 @@ google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/search-service/test/server_test.go b/code/services/search-service/test/server_test.go index 4883d35..93f2d58 100644 --- a/code/services/search-service/test/server_test.go +++ b/code/services/search-service/test/server_test.go @@ -13,24 +13,25 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/grpc" ) type MockCommunityClient struct { communitypb.CommunityServiceClient - ListCommunitiesFunc func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) + ListCommunitiesFunc func(ctx context.Context, req *communitypb.ListCommunitiesRequest, opts ...grpc.CallOption) (*communitypb.ListCommunitiesResponse, error) } -func (m *MockCommunityClient) ListCommunities(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { - return m.ListCommunitiesFunc(ctx, req) +func (m *MockCommunityClient) ListCommunities(ctx context.Context, req *communitypb.ListCommunitiesRequest, opts ...grpc.CallOption) (*communitypb.ListCommunitiesResponse, error) { + return m.ListCommunitiesFunc(ctx, req, opts...) } type MockThreadClient struct { threadpb.ThreadServiceClient - ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) + ListThreadsFunc func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) } -func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { - return m.ListThreadsFunc(ctx, req) +func (m *MockThreadClient) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req, opts...) } func TestGlobalSearch_Validation(t *testing.T) { @@ -75,14 +76,14 @@ func TestGlobalSearch_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.SearchServer{ CommunityClient: &MockCommunityClient{ - ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { + ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest, opts ...grpc.CallOption) (*communitypb.ListCommunitiesResponse, error) { return &communitypb.ListCommunitiesResponse{ Communities: []*models.Community{}, }, nil }, }, ThreadClient: &MockThreadClient{ - ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { return &threadpb.ListThreadsResponse{ Threads: []*models.Thread{}, }, nil @@ -142,7 +143,7 @@ func TestCommunitySearch_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.SearchServer{ CommunityClient: &MockCommunityClient{ - ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { + ListCommunitiesFunc: func(ctx context.Context, req *communitypb.ListCommunitiesRequest, opts ...grpc.CallOption) (*communitypb.ListCommunitiesResponse, error) { return &communitypb.ListCommunitiesResponse{ Communities: []*models.Community{}, }, nil @@ -202,7 +203,7 @@ func TestThreadSearch_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.SearchServer{ ThreadClient: &MockThreadClient{ - ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { + ListThreadsFunc: func(ctx context.Context, req *threadpb.ListThreadsRequest, opts ...grpc.CallOption) (*threadpb.ListThreadsResponse, error) { return &threadpb.ListThreadsResponse{ Threads: []*models.Thread{}, }, nil diff --git a/code/services/thread-service/go.mod b/code/services/thread-service/go.mod index 6cb5ac3..a0aa6a9 100644 --- a/code/services/thread-service/go.mod +++ b/code/services/thread-service/go.mod @@ -11,13 +11,16 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.10.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gen => ../../gen diff --git a/code/services/thread-service/go.sum b/code/services/thread-service/go.sum index 7c94794..0cca277 100644 --- a/code/services/thread-service/go.sum +++ b/code/services/thread-service/go.sum @@ -4,6 +4,7 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= @@ -24,6 +25,7 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5uk github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -63,5 +65,7 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/thread-service/test/server_test.go b/code/services/thread-service/test/server_test.go index 6545926..242bcd0 100644 --- a/code/services/thread-service/test/server_test.go +++ b/code/services/thread-service/test/server_test.go @@ -14,44 +14,50 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/grpc" ) type MockDBClient struct { dbpb.DBServiceClient - ListThreadsFunc func(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) - CreateThreadFunc func(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) - GetThreadFunc func(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) - UpdateThreadFunc func(ctx context.Context, req *dbpb.UpdateThreadRequest) (*emptypb.Empty, error) - DeleteThreadFunc func(ctx context.Context, req *dbpb.DeleteThreadRequest) (*emptypb.Empty, error) + ListThreadsFunc func(ctx context.Context, req *dbpb.ListThreadsRequest, opts ...grpc.CallOption) (*dbpb.ListThreadsResponse, error) + CreateThreadFunc func(ctx context.Context, req *dbpb.CreateThreadRequest, opts ...grpc.CallOption) (*dbpb.CreateThreadResponse, error) + GetThreadFunc func(ctx context.Context, req *dbpb.GetThreadRequest, opts ...grpc.CallOption) (*models.Thread, error) + UpdateThreadFunc func(ctx context.Context, req *dbpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeleteThreadFunc func(ctx context.Context, req *dbpb.DeleteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockDBClient) ListThreads(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) { - return m.ListThreadsFunc(ctx, req) +func (m *MockDBClient) ListThreads(ctx context.Context, req *dbpb.ListThreadsRequest, opts ...grpc.CallOption) (*dbpb.ListThreadsResponse, error) { + return m.ListThreadsFunc(ctx, req, opts...) } -func (m *MockDBClient) CreateThread(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) { - return m.CreateThreadFunc(ctx, req) +func (m *MockDBClient) CreateThread(ctx context.Context, req *dbpb.CreateThreadRequest, opts ...grpc.CallOption) (*dbpb.CreateThreadResponse, error) { + return m.CreateThreadFunc(ctx, req, opts...) } -func (m *MockDBClient) GetThread(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) { - return m.GetThreadFunc(ctx, req) +func (m *MockDBClient) GetThread(ctx context.Context, req *dbpb.GetThreadRequest, opts ...grpc.CallOption) (*models.Thread, error) { + return m.GetThreadFunc(ctx, req, opts...) } -func (m *MockDBClient) UpdateThread(ctx context.Context, req *dbpb.UpdateThreadRequest) (*emptypb.Empty, error) { - return m.UpdateThreadFunc(ctx, req) +func (m *MockDBClient) UpdateThread(ctx context.Context, req *dbpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req, opts...) } -func (m *MockDBClient) DeleteThread(ctx context.Context, req *dbpb.DeleteThreadRequest) (*emptypb.Empty, error) { - return m.DeleteThreadFunc(ctx, req) +func (m *MockDBClient) DeleteThread(ctx context.Context, req *dbpb.DeleteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.DeleteThreadFunc(ctx, req, opts...) } type MockCommunityClient struct { communitypb.CommunityServiceClient - GetCommunityFunc func(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) + GetCommunityFunc func(ctx context.Context, req *communitypb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) + UpdateCommunityFunc func(ctx context.Context, req *communitypb.UpdateCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockCommunityClient) GetCommunity(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) { - return m.GetCommunityFunc(ctx, req) +func (m *MockCommunityClient) GetCommunity(ctx context.Context, req *communitypb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) { + return m.GetCommunityFunc(ctx, req, opts...) +} + +func (m *MockCommunityClient) UpdateCommunity(ctx context.Context, req *communitypb.UpdateCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateCommunityFunc(ctx, req, opts...) } func TestCreateThread_Validation(t *testing.T) { @@ -95,14 +101,22 @@ func TestCreateThread_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.ThreadServer{ DBClient: &MockDBClient{ - CreateThreadFunc: func(ctx context.Context, req *dbpb.CreateThreadRequest) (*dbpb.CreateThreadResponse, error) { - return &dbpb.CreateThreadResponse{ - Id: "123", + CreateThreadFunc: func(ctx context.Context, req *dbpb.CreateThreadRequest, opts ...grpc.CallOption) (*dbpb.CreateThreadResponse, error) { + return &dbpb.CreateThreadResponse{Id: "123"}, nil + }, + }, + CommunityClient: &MockCommunityClient{ + GetCommunityFunc: func(ctx context.Context, req *communitypb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) { + return &models.Community{ + Id: "123", + Name: "test-community", }, nil }, + UpdateCommunityFunc: func(ctx context.Context, req *communitypb.UpdateCommunityRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil + }, }, } - _, err := server.CreateThread(context.Background(), tt.req) if tt.wantErr != nil { assert.Equal(t, tt.wantErr.Error(), err.Error()) @@ -137,7 +151,7 @@ func TestGetThread_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.ThreadServer{ DBClient: &MockDBClient{ - GetThreadFunc: func(ctx context.Context, req *dbpb.GetThreadRequest) (*models.Thread, error) { + GetThreadFunc: func(ctx context.Context, req *dbpb.GetThreadRequest, opts ...grpc.CallOption) (*models.Thread, error) { return &models.Thread{ Id: "123", CommunityId: "456", @@ -175,19 +189,19 @@ func TestListThreads_Validation(t *testing.T) { wantErr: status.Error(codes.InvalidArgument, "Title cannot be empty"), }, { - name: "negative offset", - req: &threadpb.ListThreadsRequest{Offset: int32Ptr(-1)}, - wantErr: status.Error(codes.InvalidArgument, "Offset cannot be negative"), + name: "negative_offset", + req: &threadpb.ListThreadsRequest{CommunityId: strPtr("123"), Offset: int32Ptr(-1), Limit: int32Ptr(10), SortBy: strPtr("new")}, + wantErr: status.Error(codes.InvalidArgument, "Offset must be a positive integer"), }, { - name: "zero limit", - req: &threadpb.ListThreadsRequest{Limit: int32Ptr(0)}, - wantErr: status.Error(codes.InvalidArgument, "Limit must be positive"), + name: "zero_limit", + req: &threadpb.ListThreadsRequest{CommunityId: strPtr("123"), Offset: int32Ptr(0), Limit: int32Ptr(0), SortBy: strPtr("new")}, + wantErr: status.Error(codes.InvalidArgument, "Limit must be a positive integer"), }, { - name: "empty sort", - req: &threadpb.ListThreadsRequest{SortBy: strPtr("")}, - wantErr: status.Error(codes.InvalidArgument, "Sort by cannot be empty"), + name: "empty_sort", + req: &threadpb.ListThreadsRequest{CommunityId: strPtr("123"), Offset: int32Ptr(0), Limit: int32Ptr(10), SortBy: strPtr("")}, + wantErr: status.Error(codes.InvalidArgument, "Sort cannot be empty"), }, { name: "valid request", @@ -206,14 +220,14 @@ func TestListThreads_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.ThreadServer{ DBClient: &MockDBClient{ - ListThreadsFunc: func(ctx context.Context, req *dbpb.ListThreadsRequest) (*dbpb.ListThreadsResponse, error) { + ListThreadsFunc: func(ctx context.Context, req *dbpb.ListThreadsRequest, opts ...grpc.CallOption) (*dbpb.ListThreadsResponse, error) { return &dbpb.ListThreadsResponse{ Threads: []*models.Thread{}, }, nil }, }, CommunityClient: &MockCommunityClient{ - GetCommunityFunc: func(ctx context.Context, req *communitypb.GetCommunityRequest) (*models.Community, error) { + GetCommunityFunc: func(ctx context.Context, req *communitypb.GetCommunityRequest, opts ...grpc.CallOption) (*models.Community, error) { return &models.Community{ Id: "123", Name: "test-community", diff --git a/code/services/vote-service/go.mod b/code/services/vote-service/go.mod index 15a034a..5bdbb93 100644 --- a/code/services/vote-service/go.mod +++ b/code/services/vote-service/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( gen v0.0.0-00010101000000-000000000000 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.6 ) @@ -14,7 +14,9 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect diff --git a/code/services/vote-service/go.sum b/code/services/vote-service/go.sum index cd6e1f2..8b1570d 100644 --- a/code/services/vote-service/go.sum +++ b/code/services/vote-service/go.sum @@ -1,3 +1,5 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -11,8 +13,18 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= @@ -40,4 +52,7 @@ google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/code/services/vote-service/test/server_test.go b/code/services/vote-service/test/server_test.go index bb17d60..807473f 100644 --- a/code/services/vote-service/test/server_test.go +++ b/code/services/vote-service/test/server_test.go @@ -13,24 +13,25 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/grpc" ) type MockThreadClient struct { threadpb.ThreadServiceClient - UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) + UpdateThreadFunc func(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { - return m.UpdateThreadFunc(ctx, req) +func (m *MockThreadClient) UpdateThread(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateThreadFunc(ctx, req, opts...) } type MockCommentClient struct { commentpb.CommentServiceClient - UpdateCommentFunc func(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) + UpdateCommentFunc func(ctx context.Context, req *commentpb.UpdateCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } -func (m *MockCommentClient) UpdateComment(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) { - return m.UpdateCommentFunc(ctx, req) +func (m *MockCommentClient) UpdateComment(ctx context.Context, req *commentpb.UpdateCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return m.UpdateCommentFunc(ctx, req, opts...) } func TestUpvoteThread_Validation(t *testing.T) { @@ -57,7 +58,7 @@ func TestUpvoteThread_Validation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := &src.VoteServer{ ThreadClient: &MockThreadClient{ - UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest) (*emptypb.Empty, error) { + UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { return &emptypb.Empty{}, nil }, }, @@ -97,9 +98,13 @@ func TestUpvoteComment_Validation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := &src.VoteServer{ - ThreadClient: &MockThreadClient{}, + ThreadClient: &MockThreadClient{ + UpdateThreadFunc: func(ctx context.Context, req *threadpb.UpdateThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil + }, + }, CommentClient: &MockCommentClient{ - UpdateCommentFunc: func(ctx context.Context, req *commentpb.UpdateCommentRequest) (*emptypb.Empty, error) { + UpdateCommentFunc: func(ctx context.Context, req *commentpb.UpdateCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { return &emptypb.Empty{}, nil }, }, From c6fe069cb413e51145f2f8de125d0210405e2828 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 May 2025 14:30:47 +0100 Subject: [PATCH 4/7] Fix main.yaml --- .github/workflows/main.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 45aa5d2..7e1697a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -43,10 +43,6 @@ jobs: working-directory: code/services/popular-service run: go test ./test/... - - name: Run unit tests for db-service - working-directory: code/services/db-service - run: go test ./test/... - - name: Run unit tests for community-service working-directory: code/services/community-service run: go test ./test/... From cbd09548cb9d5b47c5130033a0582f9883a7d2f1 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 29 May 2025 14:30:47 +0100 Subject: [PATCH 5/7] Fix main.yaml --- .github/workflows/main.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 45aa5d2..c043b28 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,7 +11,7 @@ env: CLUSTER_NAME: threadit-cluster ZONE: europe-west1-b GCS_KEY: gcs-key - SERVICES: community thread comment vote search popular + SERVICES: db community thread comment vote search popular jobs: test: @@ -43,10 +43,6 @@ jobs: working-directory: code/services/popular-service run: go test ./test/... - - name: Run unit tests for db-service - working-directory: code/services/db-service - run: go test ./test/... - - name: Run unit tests for community-service working-directory: code/services/community-service run: go test ./test/... From a3a69bf667bb7f022c2b672abc12745f5484ab47 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 2 Jun 2025 17:17:21 +0100 Subject: [PATCH 6/7] Remove Tests Script --- code/services/run_tests.sh | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 code/services/run_tests.sh diff --git a/code/services/run_tests.sh b/code/services/run_tests.sh deleted file mode 100644 index dac00b7..0000000 --- a/code/services/run_tests.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -services=( - "vote-service" - "thread-service" - "search-service" - "popular-service" - "community-service" - "comment-service" -) - -for service in "${services[@]}"; do - echo "Running tests for $service..." - cd "$service/test" || { - echo "Failed to change directory to $service/test" - continue - } - - # Run the tests - go test -v ./... - - # Return to the services directory - cd ../.. - - echo "----------------------------------------" -done - -echo "All tests completed!" \ No newline at end of file From 3a47e19702e4af1a6b7d9e51f4f5f441e74a45d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Proen=C3=A7a?= Date: Mon, 2 Jun 2025 17:26:35 +0100 Subject: [PATCH 7/7] Divided workflow into dev and main --- .github/workflows/dev.yml | 47 +++++++++++++++++++++++ .github/workflows/{main.yaml => main.yml} | 39 +------------------ docs/phases/phase8.md | 2 +- 3 files changed, 49 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/dev.yml rename .github/workflows/{main.yaml => main.yml} (79%) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..4410f22 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,47 @@ +name: Build and Test + +on: + push: + branches: + - dev + pull_request: + branches: + - dev + +jobs: + test: + name: Run Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Run unit tests for thread-service + working-directory: code/services/thread-service + run: go test ./test/... + + - name: Run unit tests for vote-service + working-directory: code/services/vote-service + run: go test ./test/... + + - name: Run unit tests for search-service + working-directory: code/services/search-service + run: go test ./test/... + + - name: Run unit tests for popular-service + working-directory: code/services/popular-service + run: go test ./test/... + + - name: Run unit tests for community-service + working-directory: code/services/community-service + run: go test ./test/... + + - name: Run unit tests for comment-service + working-directory: code/services/comment-service + run: go test ./test/... diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yml similarity index 79% rename from .github/workflows/main.yaml rename to .github/workflows/main.yml index c043b28..ce7cd04 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yml @@ -14,43 +14,6 @@ env: SERVICES: db community thread comment vote search popular jobs: - test: - name: Run Unit Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.23' - - - name: Run unit tests for thread-service - working-directory: code/services/thread-service - run: go test ./test/... - - - name: Run unit tests for vote-service - working-directory: code/services/vote-service - run: go test ./test/... - - - name: Run unit tests for search-service - working-directory: code/services/search-service - run: go test ./test/... - - - name: Run unit tests for popular-service - working-directory: code/services/popular-service - run: go test ./test/... - - - name: Run unit tests for community-service - working-directory: code/services/community-service - run: go test ./test/... - - - name: Run unit tests for comment-service - working-directory: code/services/comment-service - run: go test ./test/... - check-cluster: name: Check if GKE cluster exists runs-on: ubuntu-latest @@ -82,7 +45,7 @@ jobs: build-publish-deploy: name: Build, Publish, and Deploy - needs: [test, check-cluster] + needs: check-cluster if: needs.check-cluster.outputs.exists == 'true' runs-on: ubuntu-latest environment: production diff --git a/docs/phases/phase8.md b/docs/phases/phase8.md index 74ba814..2ca6b8b 100644 --- a/docs/phases/phase8.md +++ b/docs/phases/phase8.md @@ -1,3 +1,3 @@ ## 🔍 Phase 8 - Automation with CI/CD -The CI/CD workflow can be found [here](../../.github/workflows/main.yaml). \ No newline at end of file +The CI/CD workflow can be found [here](../../.github/workflows). \ No newline at end of file