package usecase import ( "context" "encoding/base64" "errors" "testing" "time" "code.30cm.net/digimon/app-cloudep-comment-server/pkg/domain/comment" "code.30cm.net/digimon/app-cloudep-comment-server/pkg/domain/entity" "code.30cm.net/digimon/app-cloudep-comment-server/pkg/domain/repository" "code.30cm.net/digimon/app-cloudep-comment-server/pkg/domain/usecase" "github.com/golang/snappy" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/mock/gomock" mockRepo "code.30cm.net/digimon/app-cloudep-comment-server/pkg/mock/repository" err "code.30cm.net/digimon/app-cloudep-comment-server/pkg/repository" ) func TestCommentUseCase_CreateCommentItem(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommentRepository := mockRepo.NewMockCommentRepository(mockCtrl) uc := MustCommentUseCase(CommentUseCaseParam{ Comment: mockCommentRepository, }) tests := []struct { name string req usecase.CreateCommentDocs mockSetup func() wantErr bool }{ { name: "successfully create top-level comment", req: usecase.CreateCommentDocs{ UID: "user-123", ReferenceID: "ref-001", Message: "This is a top-level comment", }, mockSetup: func() { mockCommentRepository.EXPECT(). CreateComment(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, e *entity.Comments) error { // 驗證插入內容是否正確 assert.Equal(t, "user-123", e.UID) assert.Equal(t, "ref-001", e.ReferenceID) assert.Equal(t, comment.TopLevelComment, e.Level) return nil }) }, wantErr: false, }, { name: "successfully create sub-level comment", req: usecase.CreateCommentDocs{ UID: "user-456", ReferenceID: "ref-002", ParentCommentID: "parent-001", Message: "This is a sub-level comment", }, mockSetup: func() { mockCommentRepository.EXPECT(). CreateComment(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, e *entity.Comments) error { // 驗證插入內容是否正確 assert.Equal(t, "user-456", e.UID) assert.Equal(t, "ref-002", e.ReferenceID) assert.Equal(t, "parent-001", e.ParentCommentID) assert.Equal(t, comment.SubLevelComment, e.Level) return nil }) }, wantErr: false, }, { name: "failed to create comment due to repository error", req: usecase.CreateCommentDocs{ UID: "user-789", ReferenceID: "ref-003", Message: "This is a failing comment", }, mockSetup: func() { mockCommentRepository.EXPECT(). CreateComment(gomock.Any(), gomock.Any()). Return(errors.New("database error")) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.mockSetup() err := uc.CreateCommentItem(context.Background(), tt.req) if tt.wantErr { assert.Error(t, err) if err != nil { assert.Contains(t, err.Error(), "failed to create comment") } } else { assert.NoError(t, err) } }) } } func TestCommentUseCase_GetCommentItem(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockCommentRepository := mockRepo.NewMockCommentRepository(mockCtrl) uc := MustCommentUseCase(CommentUseCaseParam{ Comment: mockCommentRepository, }) tests := []struct { name string id string mockSetup func() wantErr bool expected *usecase.CommentDocs }{ { name: "successfully get comment item", id: "64a1f2e9347e1a001d345678", mockSetup: func() { mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, req repository.ListCommentRequest) ([]*entity.Comments, int64, error) { assert.Equal(t, "64a1f2e9347e1a001d345678", *req.ReferenceID) return []*entity.Comments{ { ID: primitive.NewObjectID(), UID: "user-123", ReferenceID: "64a1f2e9347e1a001d345678", ParentCommentID: "", Message: base64.StdEncoding.EncodeToString(snappy.Encode(nil, []byte("This is a test comment"))), UpdatedAt: time.Now().UTC().UnixNano(), CreatedAt: time.Now().UTC().UnixNano(), }, }, 1, nil }) }, wantErr: false, expected: &usecase.CommentDocs{ UID: "user-123", ReferenceID: "64a1f2e9347e1a001d345678", ParentCommentID: "", Message: "This is a test comment", }, }, { name: "comment not found", id: "64a1f2e9347e1a001d345679", mockSetup: func() { mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). Return([]*entity.Comments{}, int64(0), err.ErrNotFound) }, wantErr: true, }, { name: "database error", id: "64a1f2e9347e1a001d345680", mockSetup: func() { mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). Return([]*entity.Comments{}, int64(0), errors.New("database error")) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.mockSetup() comment, err := uc.GetCommentItem(context.Background(), tt.id) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.NotNil(t, comment) assert.Equal(t, tt.expected.UID, comment.UID) assert.Equal(t, tt.expected.ReferenceID, comment.ReferenceID) assert.Equal(t, tt.expected.Message, comment.Message) assert.Equal(t, tt.expected.ParentCommentID, comment.ParentCommentID) } }) } } func TestCommentUseCase_UpdateCommentMessage(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() // Mock Repository mockCommentRepository := mockRepo.NewMockCommentRepository(mockCtrl) // UseCase Initialization uc := MustCommentUseCase(CommentUseCaseParam{ Comment: mockCommentRepository, }) tests := []struct { name string id string message string mockSetup func() wantErr bool }{ { name: "successfully update comment message", id: "64a1f2e9347e1a001d345678", // Valid ObjectID message: "Updated comment message", mockSetup: func() { mockCommentRepository.EXPECT(). UpdateCommentMessage(gomock.Any(), "64a1f2e9347e1a001d345678", "Updated comment message"). Return(nil) }, wantErr: false, }, { name: "invalid ObjectID format", id: "invalid-id", // Invalid ObjectID message: "Invalid ID test", mockSetup: func() { mockCommentRepository.EXPECT(). UpdateCommentMessage(gomock.Any(), "invalid-id", "Invalid ID test"). Return(err.ErrInvalidObjectID) }, wantErr: true, }, { name: "comment not found", id: "64a1f2e9347e1a001d345679", // Non-existent ObjectID message: "Non-existent comment", mockSetup: func() { mockCommentRepository.EXPECT(). UpdateCommentMessage(gomock.Any(), "64a1f2e9347e1a001d345679", "Non-existent comment"). Return(err.ErrNotFound) }, wantErr: true, }, { name: "database error while updating", id: "64a1f2e9347e1a001d345680", message: "Database error test", mockSetup: func() { mockCommentRepository.EXPECT(). UpdateCommentMessage(gomock.Any(), "64a1f2e9347e1a001d345680", "Database error test"). Return(errors.New("database error")) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Mock Setup tt.mockSetup() // Execute UseCase err := uc.UpdateCommentMessage(context.Background(), tt.id, tt.message) // Assert Results if tt.wantErr { assert.Error(t, err) if err != nil { assert.Contains(t, err.Error(), "failed to update comment") } } else { assert.NoError(t, err) } }) } } func TestCommentUseCase_RemoveCommentItem(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() // Mock Repository mockCommentRepository := mockRepo.NewMockCommentRepository(mockCtrl) // UseCase Initialization uc := MustCommentUseCase(CommentUseCaseParam{ Comment: mockCommentRepository, }) tests := []struct { name string id string mockSetup func() wantErr bool }{ { name: "successfully remove comment item", id: "64a1f2e9347e1a001d345678", // Valid ObjectID mockSetup: func() { mockCommentRepository.EXPECT(). DeleteCommentByID(gomock.Any(), "64a1f2e9347e1a001d345678"). Return(nil) }, wantErr: false, }, { name: "invalid ObjectID format", id: "invalid-id", // Invalid ObjectID mockSetup: func() { mockCommentRepository.EXPECT(). DeleteCommentByID(gomock.Any(), "invalid-id"). Return(err.ErrInvalidObjectID) }, wantErr: true, }, { name: "comment not found", id: "64a1f2e9347e1a001d345679", // Non-existent ObjectID mockSetup: func() { mockCommentRepository.EXPECT(). DeleteCommentByID(gomock.Any(), "64a1f2e9347e1a001d345679"). Return(err.ErrNotFound) }, wantErr: true, }, { name: "database error while deleting", id: "64a1f2e9347e1a001d345680", mockSetup: func() { mockCommentRepository.EXPECT(). DeleteCommentByID(gomock.Any(), "64a1f2e9347e1a001d345680"). Return(errors.New("database error")) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Mock Setup tt.mockSetup() // Execute UseCase err := uc.RemoveCommentItem(context.Background(), tt.id) // Assert Results if tt.wantErr { assert.Error(t, err) if err != nil { assert.Contains(t, err.Error(), "failed to del comment") } } else { assert.NoError(t, err) } }) } } func TestCommentUseCase_ListComment(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() // Mock Repository mockCommentRepository := mockRepo.NewMockCommentRepository(mockCtrl) // UseCase Initialization uc := MustCommentUseCase(CommentUseCaseParam{ Comment: mockCommentRepository, }) tests := []struct { name string req usecase.ListCommentRequest mockSetup func() wantErr bool expected []*usecase.CommentDocs total int64 }{ { name: "successfully list comments with replies", req: usecase.ListCommentRequest{ PageSize: 10, PageIndex: 1, }, mockSetup: func() { // Mock main layer comments mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). Return([]*entity.Comments{ { ID: primitive.NewObjectID(), UID: "user-123", ReferenceID: "ref-001", Message: base64.StdEncoding.EncodeToString(snappy.Encode(nil, []byte("Comment 1"))), UpdatedAt: time.Now().UnixNano(), CreatedAt: time.Now().UnixNano(), }, }, int64(1), nil) // Mock replies mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). Return([]*entity.Comments{ { ID: primitive.NewObjectID(), UID: "user-456", ReferenceID: "ref-001", ParentCommentID: "parent-001", Message: base64.StdEncoding.EncodeToString(snappy.Encode(nil, []byte("Reply 1"))), UpdatedAt: time.Now().UnixNano(), CreatedAt: time.Now().UnixNano(), }, }, int64(1), nil) }, wantErr: false, expected: []*usecase.CommentDocs{ { UID: "user-123", ReferenceID: "ref-001", Message: "Comment 1", Replies: []*usecase.CommentDocs{ { UID: "user-456", ReferenceID: "ref-001", ParentCommentID: "parent-001", Message: "Reply 1", }, }, }, }, total: 1, }, { name: "database error while listing comments", req: usecase.ListCommentRequest{ PageSize: 10, PageIndex: 1, }, mockSetup: func() { mockCommentRepository.EXPECT(). ListComments(gomock.Any(), gomock.Any()). Return([]*entity.Comments{}, int64(0), errors.New("database error")) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Mock Setup tt.mockSetup() // Execute UseCase results, total, err := uc.ListComment(context.Background(), tt.req) // Assert Results if tt.wantErr { assert.Error(t, err) assert.Nil(t, results) assert.Equal(t, int64(0), total) } else { assert.NoError(t, err) assert.Equal(t, tt.total, total) assert.Len(t, results, len(tt.expected)) for i, expectedComment := range tt.expected { assert.Equal(t, expectedComment.Message, results[i].Message) } } }) } }