Skip to content
Merged

Dev #33

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions internal/dto/question_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (
// QuestionResponseDTO represents the structure to be used
// when returning a question object in an HTTP response.
type QuestionResponseDTO struct {
ID *primitive.ObjectID `json:"id"`
CourseID *uuid.UUID `json:"course_id"`
Title *string `json:"title"`
Description *string `json:"description"`
Tags *[]int `json:"tags"`
AuthorID *uuid.UUID `json:"authorid"`
Votes *int `json:"votes"`
UserVote *int `json:"user_vote"` // 1 for upvote, -1 for downvote, 0 for no vote
CreatedAt *time.Time `json:"created_at"`
Answers *[]AnswerResponseDTO `json:"answers"`
ID *primitive.ObjectID `json:"id"`
CourseID *uuid.UUID `json:"course_id"`
Title *string `json:"title"`
Description *string `json:"description"`
Tags *[]int `json:"tags"`
AuthorID *uuid.UUID `json:"authorid"`
Votes *int `json:"votes"`
UserVote *int `json:"user_vote"` // 1 for upvote, -1 for downvote, 0 for no vote
CreatedAt *time.Time `json:"created_at"`
CorrectAnswer *primitive.ObjectID `json:"correct_answer"` // id of the correct answer
Answers *[]AnswerResponseDTO `json:"answers"`
}
6 changes: 6 additions & 0 deletions internal/repository/forum/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"go.mongodb.org/mongo-driver/mongo/options"
)


// MockCollection es una implementación mock de MongoCollection
type MockCollection struct {
mock.Mock
Expand Down Expand Up @@ -979,6 +980,11 @@ func TestAnswerDeleteCorrect(t *testing.T) {
})
}

type Cursor interface {
All(context.Context, interface{}) error
Close(context.Context) error
}

// MockCursor es una implementación mock de mongo.Cursor
type MockCursor struct {
mock.Mock
Expand Down
4 changes: 2 additions & 2 deletions internal/router/answer_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"go.mongodb.org/mongo-driver/mongo"
)

func InitAnswerRoutes(r *gin.Engine, db *mongo.Database, conf *config.Config) {
forumRepository := forum_repository.NewForumRepository(db.Collection("forum"), conf)
func InitAnswerRoutes(r *gin.Engine, forumCollection *mongo.Collection, conf *config.Config) {
forumRepository := forum_repository.NewForumRepository(forumCollection, conf)
pushNotifier := notification.NewPushNotifier(&conf.NOTIFICATIONS_SERVICE, conf)
questionService := answer_service.NewAnswerService(forumRepository, pushNotifier, conf)
answerHandler := answer_handler.NewAnswerHandler(questionService, conf)
Expand Down
4 changes: 2 additions & 2 deletions internal/router/question_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"go.mongodb.org/mongo-driver/mongo"
)

func InitQuestionRoutes(r *gin.Engine, db *mongo.Database, conf *config.Config) {
forumRepository := forum_repository.NewForumRepository(db.Collection("forum"), conf)
func InitQuestionRoutes(r *gin.Engine, forumCollection *mongo.Collection, conf *config.Config) {
forumRepository := forum_repository.NewForumRepository(forumCollection, conf)
pushNotifier := notifications.NewPushNotifier(&conf.NOTIFICATIONS_SERVICE, conf)
questionService := question_service.NewQuestionService(forumRepository, pushNotifier, conf)
questionHandler := question_handler.NewQuestionHandler(questionService, conf)
Expand Down
10 changes: 8 additions & 2 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ func SetupRouter(db *mongo.Database, conf *config.Config) *gin.Engine {
r.Use(middleware.ErrorHandlerMiddleware(conf))
r.Use(middleware.JWTAuthMiddleware(conf))

InitQuestionRoutes(r, db, conf)
InitAnswerRoutes(r, db, conf)
var forumCollection *mongo.Collection
if db != nil {
forumCollection = db.Collection("forum")
}


InitQuestionRoutes(r, forumCollection, conf)
InitAnswerRoutes(r, forumCollection, conf)
InitSwaggerRouter(r)

return r
Expand Down
51 changes: 51 additions & 0 deletions internal/router/router_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package router

import (
"fmt"
"testing"
"forum-microservice/config"

"github.com/stretchr/testify/assert"
)

func TestSetupRouter_RegistersExpectedRoutes(t *testing.T) {
conf := &config.Config{
SV_ENVIRONMENT: config.DEVELOPMENT,
}
router := SetupRouter(nil, conf)

expectedRoutes := []string{
// Swagger
"GET /swagger/*any",

// Question
"POST /:courseId/question",
"PUT /question/:questionId",
"DELETE /question/:questionId",
"GET /:courseId/questions",
"GET /question/:questionId",
"POST /question/:questionId/upvote",
"POST /question/:questionId/downvote",
"DELETE /question/:questionId/vote",

// Answer
"POST /question/:questionId/answer",
"PUT /question/:questionId/answer/:answerId",
"DELETE /question/:questionId/answer/:answerId",
"POST /question/:questionId/answer/:answerId/upvote",
"POST /question/:questionId/answer/:answerId/downvote",
"DELETE /question/:questionId/answer/:answerId/vote",
"PUT /question/:questionId/answer/:answerId/correct",
"DELETE /question/:questionId/answer/correct",
}

actualRoutes := make(map[string]bool)
for _, route := range router.Routes() {
key := fmt.Sprintf("%s %s", route.Method, route.Path)
actualRoutes[key] = true
}

for _, expected := range expectedRoutes {
assert.True(t, actualRoutes[expected], "Expected route not registered: %s", expected)
}
}
23 changes: 22 additions & 1 deletion internal/services/answers/answer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ func TestAnswer_PostAnswer(t *testing.T) {
assert.NoError(t, err)
}

func TestAnswer_PostAnswer_QuestionNotFound(t *testing.T) {
uid := uuid.New()
binaryUUID := utils.UuidToBinary(uid)
a := models.Answer{
Content: "respuesta a pregunta inexistente",
AuthorID: binaryUUID,
}
nonExistentQID := primitive.NewObjectID()

err := answerService.PostAnswer(t.Context(), &a, &nonExistentQID)
assert.Error(t, err)
}


/* EDIT ANSWER */

Expand Down Expand Up @@ -263,6 +276,15 @@ func TestAnswer_DeleteVoteAnswer(t *testing.T) {
assert.NoError(t, err2)
}

func TestAnswer_DeleteVoteAnswer_NeverVoted(t *testing.T) {
q_id := q_ids[1]
a_id := a_ids[1]
user := binaryUUIDs[2]

err := answerService.DeleteVoteAnswer(context.Background(), &q_id, &a_id, &user)
assert.Error(t, err)
}


/* ANSWER SELECT CORRECT */
// TestAnswer_SelectCorrectAnswer tests the SelectCorrectAnswer method of the AnswerService.
Expand All @@ -281,7 +303,6 @@ func TestAnswer_SelectCorrectAnswer(t *testing.T) {
err := answerService.SelectCorrectAnswer(ctx, &q_id, &a_id, &userBinary)
assert.NoError(t, err)
}

func TestAnswer_SelectCorrectAnswer_NotFound(t *testing.T) {
// Tomamos una de las preguntas creadas en TestMain
q_id := q_ids[0]
Expand Down
85 changes: 85 additions & 0 deletions internal/utils/notifications/push_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package notification

import (
"net/http"
"net/http/httptest"
"testing"

"forum-microservice/config"
"forum-microservice/internal/dto"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive"
"encoding/json"
"io"
)

func TestPushNotifier_SendUpvote(t *testing.T) {
var received dto.PushRequest
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/push", r.URL.Path)
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &received)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

conf := &config.Config{}
url := server.URL
notifier := NewPushNotifier(&url, conf)

users := uuid.UUIDs{uuid.New()}
qid := primitive.NewObjectID()
notifier.SendUpvote(&users, &qid)

assert.Equal(t, "Forum Upvote", received.Title)
assert.Equal(t, "Your post has received a new upvote!", received.Body)
assert.Equal(t, qid.Hex(), received.ID)
}

func TestPushNotifier_SendCorrectAnswer(t *testing.T) {
var received dto.PushRequest
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/push", r.URL.Path)
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &received)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

conf := &config.Config{}
url := server.URL
notifier := NewPushNotifier(&url, conf)

users := uuid.UUIDs{uuid.New()}
qid := primitive.NewObjectID()
notifier.SendCorrectAnswer(&users, &qid)

assert.Equal(t, "Forum Correct Answer", received.Title)
assert.Equal(t, "Your post has been selected as the correct answer!", received.Body)
assert.Equal(t, qid.Hex(), received.ID)
}

func TestPushNotifier_SendNewAnswer(t *testing.T) {
var received dto.PushRequest
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/push", r.URL.Path)
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &received)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

conf := &config.Config{}
url := server.URL
notifier := NewPushNotifier(&url, conf)

users := uuid.UUIDs{uuid.New()}
qid := primitive.NewObjectID()
notifier.SendNewAnswer(&users, &qid)

assert.Equal(t, "Forum New Answer", received.Title)
assert.Equal(t, "Your question has received a new answer!", received.Body)
assert.Equal(t, qid.Hex(), received.ID)
}
80 changes: 80 additions & 0 deletions internal/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package utils_test

import (
"testing"

"forum-microservice/internal/utils"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func TestUuidToBinary(t *testing.T) {
u := uuid.New()
bin := utils.UuidToBinary(u)

assert.Equal(t, byte(4), bin.Subtype)
assert.Equal(t, u[:], bin.Data)
}

func TestBinaryToUUID_Success(t *testing.T) {
u := uuid.New()
bin := utils.UuidToBinary(u)

result, err := utils.BinaryToUUID(bin)
assert.NoError(t, err)
assert.Equal(t, u, result)
}

func TestBinaryToUUID_InvalidSubtype(t *testing.T) {
u := uuid.New()
bin := primitive.Binary{
Subtype: 0x00, // inválido
Data: u[:],
}

result, err := utils.BinaryToUUID(bin)
assert.Error(t, err)
assert.Equal(t, uuid.Nil, result)
}

func TestBinaryToUUID_InvalidData(t *testing.T) {
bin := primitive.Binary{
Subtype: 0x04,
Data: []byte{1, 2, 3}, // tamaño incorrecto
}

result, err := utils.BinaryToUUID(bin)
assert.Error(t, err)
assert.Equal(t, uuid.Nil, result)
}

func TestOrder_ToMongoSort(t *testing.T) {
assert.Equal(t, 1, utils.Ascending.ToMongoSort())
assert.Equal(t, -1, utils.Descending.ToMongoSort())

// Valor fuera de rango (default)
var invalidOrder utils.Order = 42
assert.Equal(t, 1, invalidOrder.ToMongoSort())
}

func TestGetUserVote(t *testing.T) {
user := primitive.Binary{Subtype: 4, Data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}
other := primitive.Binary{Subtype: 4, Data: []byte{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}}

// Caso: upvote previo
upvotes := []primitive.Binary{user}
downvotes := []primitive.Binary{}
assert.Equal(t, 1, utils.GetUserVote(upvotes, downvotes, &user))

// Caso: downvote previo
upvotes = []primitive.Binary{}
downvotes = []primitive.Binary{user}
assert.Equal(t, -1, utils.GetUserVote(upvotes, downvotes, &user))

// Caso: user no voto
upvotes = []primitive.Binary{other}
downvotes = []primitive.Binary{other}
assert.Equal(t, 0, utils.GetUserVote(upvotes, downvotes, &user))
}
Loading