Skip to content

Commit

Permalink
review: make play query optional (#8)
Browse files Browse the repository at this point in the history
Signed-off-by: Raphaël Pinson <[email protected]>
  • Loading branch information
raphink authored Oct 3, 2024
1 parent c9f2668 commit 6454e14
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 47 deletions.
78 changes: 65 additions & 13 deletions instruqt/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
127 changes: 93 additions & 34 deletions instruqt/review_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 6454e14

Please sign in to comment.