Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attemped unit tests + refactoring code #53

Closed
wants to merge 3 commits into from
Closed
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
43 changes: 29 additions & 14 deletions backend/internal/handlers/friendship/friendship.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package friendship

import (
"fmt"
"github.com/GenerateNU/nightlife/internal/models"
"github.com/gofiber/fiber/v2"
"log"
"github.com/google/uuid"
)

// POST endpoint to create friendship between two users in DB
func (s *Service) CreateFriendship(c *fiber.Ctx) error {
fmt.Println("Creating a friendship")
var req models.Friendship

var req models.Friendship
// Parse the request body and check for errors
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would recommend using errs.InvalidJSON(err). would refactor that func to be

func InvalidJSON(err error) APIError {
	return NewAPIError(http.StatusBadRequest, err)
}

so we can drill down the actual error to the client. more ergonomic since we can tell them they forgot to close with a } instead of a blanket Cannot parse JSON

"error": "Cannot parse JSON",
})
}

if err := c.BodyParser(&req); err != nil {
log.Printf("Error parsing JSON: %v, Request: %+v", err, req)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Cannot parse JSON",
})
}
// Ensure both user ids are present
if req.UserID1 == uuid.Nil || req.UserID2 == uuid.Nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Both user_id1 and user_id2 are required",
})
}

if err := s.store.CreateFriendship(c.Context(), req); err != nil {
return err
}
// Set default friendship status if not provided (just an edge case)
if req.FriendshipStatus == "" {
req.FriendshipStatus = models.Pending
}

return c.Status(fiber.StatusCreated).JSON(req)
// Call CreateFriendship method to interact with db
if err := s.store.CreateFriendship(c.Context(), req); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the central error handler, can just bubble up an errors.New("failed to create friendship")

"error": "Failed to create friendship",
})
}

// On success, return 201 and req body
return c.Status(fiber.StatusCreated).JSON(req)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good stuff

}


229 changes: 229 additions & 0 deletions backend/internal/handlers/friendship/friendship_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package friendship

import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/GenerateNU/nightlife/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)

// Mock Service structure
type MockStore struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prob want to convert this to and interface then move it to backend/internal/handlers/friendship/friendship.go. then have your actual and your mock impl the interface.

// Add fields for your mocked functions
CreateFriendshipFn func(ctx context.Context, friendship models.Friendship) error
CreatePreferencesFn func(ctx context.Context, preferences models.Preferences) error
UpdateProfilePreferencesFn func(ctx context.Context, userID uuid.UUID, pref1, pref2, pref3, pref4 string) error
DeleteAccountFn func(ctx context.Context, userID uuid.UUID) error
RemoveFriendFn func(ctx context.Context, userID uuid.UUID, friendID string) error
GetProfileByColumnFn func(ctx context.Context, column, value string) (models.Profile, error)
GetAllUsersFn func(ctx context.Context) ([]models.Profile, error)
GetAllUserRatingsFn func(ctx context.Context, userID uuid.UUID) ([]models.UserRating, error)
DeleteVenueFn func(ctx context.Context, venueID uuid.UUID) error
DeleteReviewForVenueFn func(ctx context.Context, reviewID int8) error
GetAllVenueRatingsFn func(ctx context.Context, venueID uuid.UUID) ([]models.VenueRatings, error)
GetAllTestsFn func(ctx context.Context) ([]models.Test, error)
}

// Implement all the Store interface methods for the mock

// Implement the CreateFriendship method for the MockStore
func (m *MockStore) CreateFriendship(ctx context.Context, friendship models.Friendship) error {
if m.CreateFriendshipFn != nil {
return m.CreateFriendshipFn(ctx, friendship)
}
return nil
}

// Implement the CreatePreferences method for the MockStore
func (m *MockStore) CreatePreferences(ctx context.Context, preferences models.Preferences) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mock looks right, super clean

if m.CreatePreferencesFn != nil {
return m.CreatePreferencesFn(ctx, preferences)
}
return nil
}

// Implement the UpdateProfilePreferences method for the MockStore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work abyan, looks awesome

func (m *MockStore) UpdateProfilePreferences(ctx context.Context, userID uuid.UUID, pref1, pref2, pref3, pref4 string) error {
if m.UpdateProfilePreferencesFn != nil {
return m.UpdateProfilePreferencesFn(ctx, userID, pref1, pref2, pref3, pref4)
}
return nil
}

// Implement the DeleteAccount method for the MockStore
func (m *MockStore) DeleteAccount(ctx context.Context, userID uuid.UUID) error {
if m.DeleteAccountFn != nil {
return m.DeleteAccountFn(ctx, userID)
}
return nil
}

// Implement the RemoveFriend method for the MockStore
func (m *MockStore) RemoveFriend(ctx context.Context, userID uuid.UUID, friendID string) error {
if m.RemoveFriendFn != nil {
return m.RemoveFriendFn(ctx, userID, friendID)
}
return nil
}

// Implement the GetProfileByColumn method for the MockStore
func (m *MockStore) GetProfileByColumn(ctx context.Context, column, value string) (models.Profile, error) {
if m.GetProfileByColumnFn != nil {
return m.GetProfileByColumnFn(ctx, column, value)
}
return models.Profile{}, nil
}

// Implement the GetAllUsers method for the MockStore
func (m *MockStore) GetAllUsers(ctx context.Context) ([]models.Profile, error) {
if m.GetAllUsersFn != nil {
return m.GetAllUsersFn(ctx)
}
return nil, nil
}

// Implement the GetAllUserRatings method for the MockStore
func (m *MockStore) GetAllUserRatings(ctx context.Context, userID uuid.UUID) ([]models.UserRating, error) {
if m.GetAllUserRatingsFn != nil {
return m.GetAllUserRatingsFn(ctx, userID)
}
return nil, nil
}

// Implement the DeleteVenue method for the MockStore
func (m *MockStore) DeleteVenue(ctx context.Context, venueID uuid.UUID) error {
if m.DeleteVenueFn != nil {
return m.DeleteVenueFn(ctx, venueID)
}
return nil
}

// Implement the DeleteReviewForVenue method for the MockStore
func (m *MockStore) DeleteReviewForVenue(ctx context.Context, reviewID int8) error {
if m.DeleteReviewForVenueFn != nil {
return m.DeleteReviewForVenueFn(ctx, reviewID)
}
return nil
}

// Implement the GetAllVenueRatings method for the MockStore
func (m *MockStore) GetAllVenueRatings(ctx context.Context, venueID uuid.UUID) ([]models.VenueRatings, error) {
if m.GetAllVenueRatingsFn != nil {
return m.GetAllVenueRatingsFn(ctx, venueID)
}
return nil, nil
}

// Implement the GetAllTests method for the MockStore
func (m *MockStore) GetAllTests(ctx context.Context) ([]models.Test, error) {
if m.GetAllTestsFn != nil {
return m.GetAllTestsFn(ctx)
}
return nil, nil
}

// Implement the Close method for the MockStore to satisfy the storage.Storage interface
func (m *MockStore) Close() {

}

// Test for the POST endpoint CreateFriendship
func TestCreateFriendship(t *testing.T) {
// Create a new Fiber app
app := fiber.New()

// Set up the mock store
mockStore := &MockStore{}

// Set up the service with the mock store
service := &Service{
store: mockStore,
}

// Add the route to Fiber
app.Post("/add-friend", service.CreateFriendship)

// Define the test cases
tests := []struct {
name string
inputJSON string
mockResponse error
expectedStatus int
expectedBody string
}{
{
name: "successful creation",
inputJSON: `{"user_id1": "00000000-0000-0000-0000-000000000001", "user_id2": "00000000-0000-0000-0000-000000000002"}`,
mockResponse: nil,
expectedStatus: fiber.StatusCreated,
expectedBody: `{"user_id1":"00000000-0000-0000-0000-000000000001","user_id2":"00000000-0000-0000-0000-000000000002"}`,
},
{
name: "invalid JSON body",
inputJSON: `{"invalid_json"}`,
mockResponse: nil,
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Cannot parse JSON"}`,
},
{
name: "store error",
inputJSON: `{"user_id1": "00000000-0000-0000-0000-000000000001", "user_id2": "00000000-0000-0000-0000-000000000002"}`,
mockResponse: errors.New("db error"),
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to create friendship"}`,
},
}

// Iterate over the test cases
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock the CreateFriendship function behavior based on the test case
mockStore.CreateFriendshipFn = func(_ context.Context, _ models.Friendship) error {
return tt.mockResponse
}

// Create the HTTP request
req := httptest.NewRequest(http.MethodPost, "/add-friend", bytes.NewBuffer([]byte(tt.inputJSON)))
req.Header.Set("Content-Type", "application/json")

// Test the request using Fiber's app.Test() method
resp, err := app.Test(req, -1)
if err != nil {
t.Fatalf("Error making the request: %v", err)
}

// Check the status code
if resp.StatusCode != tt.expectedStatus {
t.Errorf("expected status %d, got %d", tt.expectedStatus, resp.StatusCode)
}

// Check the response body
if tt.expectedBody != "" {
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Error unmarshalling response: %v", err)
}

// Convert the expected response from string to a map for comparison
expectedResponse := make(map[string]interface{})
if err := json.Unmarshal([]byte(tt.expectedBody), &expectedResponse); err != nil {
t.Fatalf("Error unmarshalling expected body: %v", err)
}

// Compare the actual response to the expected one
for key, value := range expectedResponse {
if response[key] != value {
t.Errorf("expected %v for key %v, got %v", value, key, response[key])
}
}
}
})
}
}
4 changes: 1 addition & 3 deletions backend/internal/handlers/friendship/routes.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package friendship

import (
//"github.com/GenerateNU/nightlife/internal/auth"
"github.com/GenerateNU/nightlife/internal/types"
"github.com/gofiber/fiber/v2"
)
Expand All @@ -13,9 +12,8 @@ func Routes(app *fiber.App, params types.Params) {

//create a grouping
protected := app.Group("/friendships")
//.Use(auth.Protected(&params.Supabase))

//create a route
protected.Post("/api/friendship", service.CreateFriendship)
protected.Post("/add-friend", service.CreateFriendship)

}
Loading