-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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{ | ||
"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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with the central error handler, can just bubble up an |
||
"error": "Failed to create friendship", | ||
}) | ||
} | ||
|
||
// On success, return 201 and req body | ||
return c.Status(fiber.StatusCreated).JSON(req) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good stuff |
||
} | ||
|
||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
// 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]) | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
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 beso 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 blanketCannot parse JSON