From 6454e14aa691d7b667d79c25cb01836117e5ff3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Pinson?= Date: Thu, 3 Oct 2024 10:22:34 +0200 Subject: [PATCH] review: make play query optional (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaƫl Pinson --- instruqt/review.go | 78 ++++++++++++++++++++---- instruqt/review_test.go | 127 +++++++++++++++++++++++++++++----------- 2 files changed, 158 insertions(+), 47 deletions(-) diff --git a/instruqt/review.go b/instruqt/review.go index 728c725..5fdf798 100644 --- a/instruqt/review.go +++ b/instruqt/review.go @@ -21,40 +21,92 @@ import ( "github.com/shurcooL/graphql" ) -// Review represents a review for an Instruqt track. -type Review struct { +// baseReview represents the fundamental fields of a review. +// It includes the review ID, score, content, and timestamps for creation and updates. +type baseReview struct { Id string `json:"-"` // The review ID. Score int `json:"score"` // The score given in the review. Content string `json:"content"` // The content of the review. - Play Play `json:"-"` // The play associated to the review. Created_At time.Time `json:"created_at"` // The timestamp when the review was created. Updated_At time.Time `json:"updated_at"` // The timestamp when the review was last updated. } -// reviewQuery represents the GraphQL query structure for fetching a single review. -type reviewQuery struct { - TrackReview Review `graphql:"trackReview(reviewID: $id)"` +// Review represents a review for an Instruqt track. +type Review struct { + baseReview + Play *Play +} + +// GetReviewOption defines a functional option for configuring GetReview. +// It allows modifying the behavior of GetReview, such as including additional fields. +type GetReviewOption func(*reviewOptions) + +// reviewOptions holds configuration options for GetReview. +// Currently, it supports whether to include the 'play' field in the query. +type reviewOptions struct { + includePlay bool // Determines if the 'play' field should be included in the query. +} + +// WithPlay is a functional option that configures GetReview to include the 'play' field in the query. +// Usage: GetReview("reviewID", WithPlay()) +func WithPlay() GetReviewOption { + return func(opts *reviewOptions) { + opts.includePlay = true + } } // GetReview retrieves a single review by its unique identifier. +// It accepts optional functional options to include additional fields like 'play'. // // Parameters: // - id (string): The unique identifier of the review. +// - opts (...Option): Variadic functional options to modify the query behavior. // // Returns: -// - *Review: A pointer to the retrieved Review. +// - *Review: A pointer to the retrieved Review. Includes Play if specified. // - error: An error object if the query fails or the review is not found. -func (c *Client) GetReview(id string) (*Review, error) { - var q reviewQuery +func (c *Client) GetReview(id string, opts ...GetReviewOption) (*Review, error) { + // Initialize default options. + options := &reviewOptions{} + for _, opt := range opts { + opt(options) + } + + // Prepare GraphQL variables. variables := map[string]interface{}{ "id": graphql.ID(id), } - // Execute the GraphQL query - err := c.GraphQLClient.Query(c.Context, &q, variables) - if err != nil { + if options.includePlay { + // Define the extended query struct with 'play'. + var q struct { + TrackReview Review `graphql:"trackReview(reviewID: $id)"` + } + + // Execute the query. + if err := c.GraphQLClient.Query(c.Context, &q, variables); err != nil { + return nil, fmt.Errorf("GraphQL query with play failed: %w", err) + } + + // Return the fetched Review, which includes Play. + return &q.TrackReview, nil + } + + // Define the base query struct without 'play'. + var q struct { + TrackReview baseReview `graphql:"trackReview(reviewID: $id)"` + } + + // Execute the query. + if err := c.GraphQLClient.Query(c.Context, &q, variables); err != nil { return nil, fmt.Errorf("GraphQL query failed: %w", err) } - return &q.TrackReview, nil + // Construct the Review without Play. + review := Review{ + baseReview: q.TrackReview, + Play: nil, // Play is not included. + } + + return &review, nil } diff --git a/instruqt/review_test.go b/instruqt/review_test.go index f8aa2e0..1bf473b 100644 --- a/instruqt/review_test.go +++ b/instruqt/review_test.go @@ -24,107 +24,166 @@ import ( "github.com/stretchr/testify/mock" ) -// TestGetReview_Success tests the successful retrieval of a review. +// TestGetReview_Success tests the successful retrieval of a review without including 'play'. func TestGetReview_Success(t *testing.T) { + // Initialize the mock GraphQL client. mockClient := new(MockGraphQLClient) client := &Client{ GraphQLClient: mockClient, - TeamSlug: "isovalent", Context: context.Background(), } - // Define the expected response - expectedReview := Review{ + // Define the expected baseReview response. + expectedBaseReview := baseReview{ + Id: "review123", Score: 5, Content: "Excellent track! Learned a lot.", Created_At: time.Now().AddDate(0, -1, 0), // 1 month ago Updated_At: time.Now(), } - // Mock the Query method + // Mock the Query method to return the expected baseReview. + mockClient.On("Query", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + // Extract the query struct to populate. + q := args.Get(1).(*struct { + TrackReview baseReview `graphql:"trackReview(reviewID: $id)"` + }) + // Assign the expected baseReview to the query result. + q.TrackReview = expectedBaseReview + }).Return(nil).Once() + + // Execute GetReview without including 'play'. + review, err := client.GetReview("review123") + + // Assertions to ensure no error and correct data is returned. + assert.NoError(t, err) + assert.NotNil(t, review) + assert.Equal(t, expectedBaseReview.Id, review.Id) + assert.Equal(t, expectedBaseReview.Score, review.Score) + assert.Equal(t, expectedBaseReview.Content, review.Content) + assert.WithinDuration(t, expectedBaseReview.Created_At, review.Created_At, time.Second) + assert.WithinDuration(t, expectedBaseReview.Updated_At, review.Updated_At, time.Second) + assert.Nil(t, review.Play) // Play should be nil since it's not included. + + // Ensure the mock expectations were met. + mockClient.AssertExpectations(t) +} + +// TestGetReview_Success_WithPlay tests the successful retrieval of a review including 'play'. +func TestGetReview_Success_WithPlay(t *testing.T) { + // Initialize the mock GraphQL client. + mockClient := new(MockGraphQLClient) + client := &Client{ + GraphQLClient: mockClient, + Context: context.Background(), + } + + // Define the expected Review response with Play. + expectedReview := Review{ + baseReview: baseReview{ + Id: "review123", + Score: 5, + Content: "Excellent track! Learned a lot.", + Created_At: time.Now().AddDate(0, -1, 0), // 1 month ago + Updated_At: time.Now(), + }, + Play: &Play{ + Id: "play456", + }, + } + + // Mock the Query method to return the expected Review with Play. mockClient.On("Query", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - // Extract the query struct to populate - q := args.Get(1).(*reviewQuery) - // Assign the expected review to the query result + // Extract the query struct to populate. + q := args.Get(1).(*struct { + TrackReview Review `graphql:"trackReview(reviewID: $id)"` + }) + // Assign the expected Review to the query result. q.TrackReview = expectedReview }).Return(nil).Once() - // Execute GetReview - review, err := client.GetReview("review-123") + // Execute GetReview with the WithPlay option to include 'play'. + review, err := client.GetReview("review123", WithPlay()) - // Assertions + // Assertions to ensure no error and correct data is returned. assert.NoError(t, err) assert.NotNil(t, review) + assert.Equal(t, expectedReview.Id, review.Id) assert.Equal(t, expectedReview.Score, review.Score) assert.Equal(t, expectedReview.Content, review.Content) assert.WithinDuration(t, expectedReview.Created_At, review.Created_At, time.Second) assert.WithinDuration(t, expectedReview.Updated_At, review.Updated_At, time.Second) + assert.NotNil(t, review.Play) // Play should be included. + assert.Equal(t, expectedReview.Play.Id, review.Play.Id) - // Ensure the mock expectations were met + // Ensure the mock expectations were met. mockClient.AssertExpectations(t) } // TestGetReview_QueryError tests the GetReview function when the GraphQL query fails. func TestGetReview_QueryError(t *testing.T) { + // Initialize the mock GraphQL client. mockClient := new(MockGraphQLClient) client := &Client{ GraphQLClient: mockClient, - TeamSlug: "isovalent", Context: context.Background(), } - // Mock the Query method to return an error + // Mock the Query method to return an error. mockClient.On("Query", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("network error")).Once() - // Execute GetReview + // Execute GetReview without including 'play'. review, err := client.GetReview("review-123") - // Assertions + // Assertions to ensure an error is returned and review is nil. assert.Error(t, err) assert.Nil(t, review) assert.Contains(t, err.Error(), "GraphQL query failed: network error") - // Ensure the mock expectations were met + // Ensure the mock expectations were met. mockClient.AssertExpectations(t) } // TestGetReview_NotFound tests the GetReview function when the review is not found. func TestGetReview_NotFound(t *testing.T) { + // Initialize the mock GraphQL client. mockClient := new(MockGraphQLClient) client := &Client{ GraphQLClient: mockClient, - TeamSlug: "isovalent", Context: context.Background(), } - // Define the expected response for a not found review - // Assuming that the API returns a Review with zero values if not found - // Alternatively, the API might return an error; adjust accordingly based on actual API behavior - - expectedReview := Review{ + // Define the expected response for a not found review. + // Assuming the API returns zero values for a non-existent review. + expectedBaseReview := baseReview{ + Id: "", Score: 0, Content: "", Created_At: time.Time{}, // Zero value Updated_At: time.Time{}, // Zero value } - // Mock the Query method + // Mock the Query method to return the expected baseReview with zero values. mockClient.On("Query", mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - q := args.Get(1).(*reviewQuery) - q.TrackReview = expectedReview + q := args.Get(1).(*struct { + TrackReview baseReview `graphql:"trackReview(reviewID: $id)"` + }) + q.TrackReview = expectedBaseReview }).Return(nil).Once() - // Execute GetReview + // Execute GetReview with a non-existent ID. review, err := client.GetReview("non-existent-id") - // Assertions + // Assertions to ensure no error is returned and review contains zero values. assert.NoError(t, err) assert.NotNil(t, review) - assert.Equal(t, 0, review.Score) - assert.Equal(t, "", review.Content) - assert.Equal(t, time.Time{}, review.Created_At) - assert.Equal(t, time.Time{}, review.Updated_At) - - // Ensure the mock expectations were met + assert.Equal(t, expectedBaseReview.Id, review.Id) + assert.Equal(t, expectedBaseReview.Score, review.Score) + assert.Equal(t, expectedBaseReview.Content, review.Content) + assert.Equal(t, expectedBaseReview.Created_At, review.Created_At) + assert.Equal(t, expectedBaseReview.Updated_At, review.Updated_At) + assert.Nil(t, review.Play) // Play should be nil since it's not included. + + // Ensure the mock expectations were met. mockClient.AssertExpectations(t) }