Skip to content
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
63 changes: 49 additions & 14 deletions pkg/api/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,38 +330,73 @@ query MyQuery {
return resp.Data.Products, nil
}

type getLatestProductVersionByIDParams struct {
type getActiveProductVersionByIDParams struct {
ID string `json:"id"`
}

type getLatestProductVersionByIDResponseData struct {
ProductVersions []ProductVersion `json:"ProductVersions"`
type getActiveProductVersionByIDProduct struct {
ActiveVersionID string `json:"active_version_id"`
}

type getLatestProductVersionByIDResponse struct {
Data getLatestProductVersionByIDResponseData `json:"data"`
type getActiveProductVersionByIDResponseData struct {
Product *getActiveProductVersionByIDProduct `json:"Products_by_pk"`
}

func (ds *Datastore) GetLatestProductVersionByID(ctx context.Context, id string) (ProductVersion, error) {
const query = `
type getActiveProductVersionByIDResponse struct {
Data getActiveProductVersionByIDResponseData `json:"data"`
}

type getProductVersionByIDResponseData struct {
ProductVersion *ProductVersion `json:"ProductVersions_by_pk"`
}

type getProductVersionByIDResponse struct {
Data getProductVersionByIDResponseData `json:"data"`
}

// GetActiveProductVersionByID returns the active (live) product version for the given product ID.
// It reads the product's active_version_id directly, avoiding selection of draft or non-live versions.
func (ds *Datastore) GetActiveProductVersionByID(ctx context.Context, id string) (ProductVersion, error) {
const getProductQuery = `
query MyQuery ($id: uuid!) {
ProductVersions(where: {Product: {active_version_id: {_is_null: false},id: {_eq: $id}}}, order_by: {created_at: desc}) {
id
Products_by_pk(id: $id) {
active_version_id
}
}
`
req := GQLRequest{
Query: query,
Variables: getLatestProductVersionByIDParams{ID: id},
Query: getProductQuery,
Variables: getActiveProductVersionByIDParams{ID: id},
}
var resp getLatestProductVersionByIDResponse
var resp getActiveProductVersionByIDResponse
if err := ds.gqlClient.Do(ctx, req, &resp); err != nil {
return ProductVersion{}, err
}
if len(resp.Data.ProductVersions) == 0 {
if resp.Data.Product == nil || resp.Data.Product.ActiveVersionID == "" {
return ProductVersion{}, ErrNotFound
}
return resp.Data.ProductVersions[0], nil

const getVersionQuery = `
query MyQuery ($id: uuid!) {
ProductVersions_by_pk(id: $id) {
id
}
}
`
activeVersionID := resp.Data.Product.ActiveVersionID
versionReq := GQLRequest{
Query: getVersionQuery,
Variables: getActiveProductVersionByIDParams{ID: activeVersionID},
}
var versionResp getProductVersionByIDResponse
if err := ds.gqlClient.Do(ctx, versionReq, &versionResp); err != nil {
return ProductVersion{}, err
}
if versionResp.Data.ProductVersion == nil || versionResp.Data.ProductVersion.ID == "" {
return ProductVersion{}, ErrNotFound
}

return *versionResp.Data.ProductVersion, nil
}

Comment on lines +379 to 401
type getLogByIDParams struct {
Expand Down
118 changes: 98 additions & 20 deletions pkg/api/datastore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -751,14 +751,14 @@ func TestListProducts(t *testing.T) {
}
}

func TestGetLatestProductVersionByID(t *testing.T) {
func TestGetActiveProductVersionByID(t *testing.T) {
httpClient := resty.New()
httpmock.ActivateNonDefault(httpClient.GetClient())
defer httpmock.DeactivateAndReset()

type mock struct {
mockResponse getLatestProductVersionByIDResponse
status int
mockResponses []interface{}
status int
}

type want struct {
Expand All @@ -774,14 +774,19 @@ func TestGetLatestProductVersionByID(t *testing.T) {
{
name: "200-happy-path",
mock: mock{
mockResponse: getLatestProductVersionByIDResponse{
Data: getLatestProductVersionByIDResponseData{
ProductVersions: []ProductVersion{
{
ID: "ProductVersion1-id",
mockResponses: []interface{}{
getActiveProductVersionByIDResponse{
Data: getActiveProductVersionByIDResponseData{
Product: &getActiveProductVersionByIDProduct{
ActiveVersionID: "ProductVersion1-id",
},
},
},
getProductVersionByIDResponse{
Data: getProductVersionByIDResponseData{
ProductVersion: &ProductVersion{ID: "ProductVersion1-id"},
},
},
},
status: http.StatusOK,
},
Expand All @@ -796,9 +801,9 @@ func TestGetLatestProductVersionByID(t *testing.T) {
{
name: "404-error",
mock: mock{
mockResponse: getLatestProductVersionByIDResponse{
Data: getLatestProductVersionByIDResponseData{
ProductVersions: []ProductVersion{},
mockResponses: []interface{}{
getActiveProductVersionByIDResponse{
Data: getActiveProductVersionByIDResponseData{},
},
},
status: http.StatusOK,
Expand All @@ -808,19 +813,92 @@ func TestGetLatestProductVersionByID(t *testing.T) {
err: ErrNotFound,
},
},
{
name: "404-error-empty-active-version-id",
mock: mock{
mockResponses: []interface{}{
getActiveProductVersionByIDResponse{
Data: getActiveProductVersionByIDResponseData{
Product: &getActiveProductVersionByIDProduct{
ActiveVersionID: "",
},
},
},
},
status: http.StatusOK,
},
want: want{
output: ProductVersion{},
err: ErrNotFound,
},
},
{
name: "404-error-active-version-not-found",
mock: mock{
mockResponses: []interface{}{
getActiveProductVersionByIDResponse{
Data: getActiveProductVersionByIDResponseData{
Product: &getActiveProductVersionByIDProduct{
ActiveVersionID: "ProductVersion1-id",
},
},
},
getProductVersionByIDResponse{
Data: getProductVersionByIDResponseData{},
},
},
status: http.StatusOK,
},
want: want{
output: ProductVersion{},
err: ErrNotFound,
},
},
// Regression: must return the product's active version, not the latest by creation order.
{
name: "200-active-version-returned-when-newer-draft-exists",
mock: mock{
mockResponses: []interface{}{
// First call: product lookup — active_version_id points to v1 (the live version),
// NOT v2 which was created more recently but is a draft without source code.
getActiveProductVersionByIDResponse{
Data: getActiveProductVersionByIDResponseData{
Product: &getActiveProductVersionByIDProduct{
ActiveVersionID: "v1-active-id",
},
},
},
// Second call: version lookup resolves v1 by its primary key.
// v2 ("v2-draft-id") is never queried — it is skipped entirely.
getProductVersionByIDResponse{
Data: getProductVersionByIDResponseData{
ProductVersion: &ProductVersion{ID: "v1-active-id"},
},
},
},
status: http.StatusOK,
},
want: want{
output: ProductVersion{ID: "v1-active-id"},
err: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

jsonData, err := json.Marshal(tt.mock.mockResponse)
if err != nil {
t.Fatalf("Error occurred during marshaling. Error: %s", err.Error())
}

mockResponse := string(jsonData)

responseCallCount := 0
httpmock.RegisterResponder("POST", "https://example.com",
func(_ *http.Request) (*http.Response, error) {
mockIndex := responseCallCount
if mockIndex >= len(tt.mock.mockResponses) {
mockIndex = len(tt.mock.mockResponses) - 1
}
jsonData, err := json.Marshal(tt.mock.mockResponses[mockIndex])
if err != nil {
t.Fatalf("Error occurred during marshaling. Error: %s", err.Error())
}
responseCallCount++
mockResponse := string(jsonData)
resp := httpmock.NewStringResponse(tt.mock.status, mockResponse)
resp.Header.Set("Content-Type", "application/json")
return resp, nil
Expand All @@ -829,7 +907,7 @@ func TestGetLatestProductVersionByID(t *testing.T) {
gqlClient := NewGraphQLClient("https://example.com", httpClient)
datastoreClient := NewDatastore(gqlClient)

output, err := datastoreClient.GetLatestProductVersionByID(t.Context(), "Product1-id")
output, err := datastoreClient.GetActiveProductVersionByID(t.Context(), "Product1-id")
if tt.want.err != nil {
require.EqualError(t, err, tt.want.err.Error())
httpmock.Reset()
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmdutil/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type DatastoreInterface interface {
GetRuntimeByName(ctx context.Context, name string) (api.Runtime, error)
GetProject(ctx context.Context, accountID, name string) (api.Project, error)
ListProducts(ctx context.Context) ([]api.Product, error)
GetLatestProductVersionByID(ctx context.Context, id string) (api.ProductVersion, error)
GetActiveProductVersionByID(ctx context.Context, id string) (api.ProductVersion, error)
ListLogsByInstanceID(ctx context.Context, instanceID string, limit int, timestamp time.Time) ([]api.Log, error)
}

Expand Down
12 changes: 6 additions & 6 deletions testutil/mocks/factory.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions vcr/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,11 @@ func askTemplate(ctx context.Context, opts *Options) error {

selectedProductID := templateOptions.IDLookup[templateLabel]

spinner = cmdutil.DisplaySpinnerMessageWithHandle(" Retrieve the latest product template version... ")
selectedProductVersion, err := opts.Datastore().GetLatestProductVersionByID(ctx, selectedProductID)
spinner = cmdutil.DisplaySpinnerMessageWithHandle(" Retrieving active product template version... ")
selectedProductVersion, err := opts.Datastore().GetActiveProductVersionByID(ctx, selectedProductID)
spinner.Stop()
if err != nil {
return fmt.Errorf("failed to get the latest product template version: %w", err)
return fmt.Errorf("failed to get the active product template version: %w", err)
}
selectedProductVersionID := selectedProductVersion.ID

Expand Down
Loading
Loading