From 742373481f2ad9cc0f7093493910f4be6e708d8d Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 7 Aug 2025 22:16:47 +0530 Subject: [PATCH 01/41] Foxx: Add GetInstalledFoxxService endpoint to fetch installed Foxx services --- v2/arangodb/client_foxx.go | 31 +++++++++++++++++++++ v2/arangodb/client_foxx_impl.go | 49 +++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 31 +++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 87561f86..224dd10f 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -36,6 +36,10 @@ type ClientFoxxService interface { InstallFoxxService(ctx context.Context, dbName string, zipFile string, options *FoxxCreateOptions) error // UninstallFoxxService uninstalls service at a given mount path. UninstallFoxxService(ctx context.Context, dbName string, options *FoxxDeleteOptions) error + // GetInstalledFoxxService retrieves the list of Foxx services installed in the specified database. + // If excludeSystem is true, system services (like _admin/aardvark) will be excluded from the result, + // returning only custom-installed Foxx services. + GetInstalledFoxxService(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceObject, error) } type FoxxCreateOptions struct { @@ -77,3 +81,30 @@ func (c *UninstallFoxxServiceRequest) modifyRequest(r connection.Request) error return nil } + +// FoxxServiceObject represents a single Foxx service installed in an ArangoDB database. +type FoxxServiceObject struct { + // Mount is the mount path of the Foxx service in the database (e.g., "/my-service"). + // This determines the URL path at which the service can be accessed. + Mount *string `json:"mount"` + + // Development indicates whether the service is in development mode. + // When true, the service is not cached and changes are applied immediately. + Development *bool `json:"development"` + + // Legacy indicates whether the service uses a legacy format or API. + // This may be used for backward compatibility checks. + Legacy *bool `json:"legacy"` + + // Provides lists the capabilities or interfaces the service provides. + // This is a flexible map that may contain metadata like API contracts or service roles. + Provides map[string]interface{} `json:"provides"` + + // Name is the name of the Foxx service (optional). + // This may be defined in the service manifest (manifest.json). + Name *string `json:"name,omitempty"` + + // Version is the version of the Foxx service (optional). + // This is useful for managing service upgrades or deployments. + Version *string `json:"version,omitempty"` +} diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 668d1aa4..339cd53a 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -21,7 +21,10 @@ package arangodb import ( "context" + "encoding/json" + "fmt" "net/http" + "net/url" "os" "github.com/arangodb/go-driver/v2/arangodb/shared" @@ -98,3 +101,49 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op return response.AsArangoErrorWithCode(code) } } + +// GetInstalledFoxxService retrieves the list of Foxx services. +func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceObject, error) { + // Ensure the URL starts with a slash + urlEndpoint := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx") + + // Append query param if needed + if excludeSystem != nil { + urlEndpoint += fmt.Sprintf("?excludeSystem=%t", *excludeSystem) + } + + // Use json.RawMessage to capture raw response for debugging + var rawResult json.RawMessage + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + // Try to unmarshal as array first + var result []FoxxServiceObject + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + // If array unmarshaling fails, try as object with result field + var objResult struct { + Result []FoxxServiceObject `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + // If both fail, return the unmarshal error + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceObject or object with result field: %s", string(rawResult)) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index fba09999..2d3452d0 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -81,3 +81,34 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) } + +func Test_GetInstalledFoxxService(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + + excludeSystem := false + services, err := client.GetInstalledFoxxService(ctx, db.Name(), &excludeSystem) + require.NoError(t, err) + require.NotEmpty(t, services) + require.GreaterOrEqual(t, len(services), 0) + + if len(services) == 0 { + t.Log("No Foxx services found.") + return + } + + for _, service := range services { + require.NotEmpty(t, service.Mount) + require.NotEmpty(t, service.Name) + require.NotEmpty(t, service.Version) + require.NotNil(t, service.Development) + require.NotNil(t, service.Provides) + require.NotNil(t, service.Legacy) + } + }) +} From 45100731c4e6d265aa3b0f076a1616140590a45f Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Fri, 8 Aug 2025 13:37:13 +0530 Subject: [PATCH 02/41] Foxx: Add endpoint to fetch detailed information about a specific Foxx service --- v2/arangodb/client_foxx.go | 112 +++++++++++++++++++++++++++++--- v2/arangodb/client_foxx_impl.go | 44 +++++++++++-- v2/tests/foxx_test.go | 26 +++++++- 3 files changed, 164 insertions(+), 18 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 224dd10f..2572c9ad 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -36,10 +36,17 @@ type ClientFoxxService interface { InstallFoxxService(ctx context.Context, dbName string, zipFile string, options *FoxxCreateOptions) error // UninstallFoxxService uninstalls service at a given mount path. UninstallFoxxService(ctx context.Context, dbName string, options *FoxxDeleteOptions) error - // GetInstalledFoxxService retrieves the list of Foxx services installed in the specified database. + // ListInstalledFoxxServices retrieves the list of Foxx services installed in the specified database. // If excludeSystem is true, system services (like _admin/aardvark) will be excluded from the result, // returning only custom-installed Foxx services. - GetInstalledFoxxService(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceObject, error) + ListInstalledFoxxServices(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceListItem, error) + // GetInstalledFoxxService retrieves detailed information about a specific Foxx service + // installed in the specified database. + // The service is identified by its mount path, which must be provided and non-empty. + // If the mount path is missing or empty, a RequiredFieldError is returned. + // The returned FoxxServiceObject contains the full metadata and configuration details + // for the specified service. + GetInstalledFoxxService(ctx context.Context, dbName string, mount *string) (FoxxServiceObject, error) } type FoxxCreateOptions struct { @@ -82,8 +89,7 @@ func (c *UninstallFoxxServiceRequest) modifyRequest(r connection.Request) error } -// FoxxServiceObject represents a single Foxx service installed in an ArangoDB database. -type FoxxServiceObject struct { +type CommonFoxxServiceFields struct { // Mount is the mount path of the Foxx service in the database (e.g., "/my-service"). // This determines the URL path at which the service can be accessed. Mount *string `json:"mount"` @@ -95,16 +101,106 @@ type FoxxServiceObject struct { // Legacy indicates whether the service uses a legacy format or API. // This may be used for backward compatibility checks. Legacy *bool `json:"legacy"` + // Name is the name of the Foxx service (optional). + // This may be defined in the service manifest (manifest.json). + Name *string `json:"name,omitempty"` + // Version is the version of the Foxx service (optional). + // This is useful for managing service upgrades or deployments. + Version *string `json:"version,omitempty"` +} + +// FoxxServiceListItem represents a single Foxx service installed in an ArangoDB database. +type FoxxServiceListItem struct { + CommonFoxxServiceFields // Provides lists the capabilities or interfaces the service provides. // This is a flexible map that may contain metadata like API contracts or service roles. Provides map[string]interface{} `json:"provides"` +} - // Name is the name of the Foxx service (optional). - // This may be defined in the service manifest (manifest.json). +// Repository describes the version control repository for the Foxx service. +type Repository struct { + // Type is the type of repository (e.g., "git"). + Type *string `json:"type,omitempty"` + + // URL is the link to the repository. + URL *string `json:"url,omitempty"` +} + +// Contributor represents a person who contributed to the Foxx service. +type Contributor struct { + // Name is the contributor's name. Name *string `json:"name,omitempty"` - // Version is the version of the Foxx service (optional). - // This is useful for managing service upgrades or deployments. + // Email is the contributor's contact email. + Email *string `json:"email,omitempty"` +} + +// Engines specifies the ArangoDB engine requirements for the Foxx service. +type Engines struct { + // Arangodb specifies the required ArangoDB version range (semver format). + Arangodb *string `json:"arangodb,omitempty"` +} + +// Manifest represents the normalized manifest.json of the Foxx service. +type Manifest struct { + // Schema is the JSON schema URL for the manifest structure. + Schema *string `json:"$schema,omitempty"` + + // Name is the name of the Foxx service. + Name *string `json:"name,omitempty"` + + // Version is the service's semantic version. Version *string `json:"version,omitempty"` + + // License is the license identifier (e.g., "Apache-2.0"). + License *string `json:"license,omitempty"` + + // Repository contains details about the service's source repository. + Repository *Repository `json:"repository,omitempty"` + + // Author is the main author of the service. + Author *string `json:"author,omitempty"` + + // Contributors is a list of people who contributed to the service. + Contributors []*Contributor `json:"contributors,omitempty"` + + // Description provides a human-readable explanation of the service. + Description *string `json:"description,omitempty"` + + // Engines specifies the engine requirements for running the service. + Engines *Engines `json:"engines,omitempty"` + + // DefaultDocument specifies the default document to serve (e.g., "index.html"). + DefaultDocument *string `json:"defaultDocument,omitempty"` + + // Main specifies the main entry point JavaScript file of the service. + Main *string `json:"main,omitempty"` + + // Configuration contains service-specific configuration options. + Configuration map[string]interface{} `json:"configuration,omitempty"` + + // Dependencies defines other services or packages this service depends on. + Dependencies map[string]interface{} `json:"dependencies,omitempty"` + + // Files maps URL paths to static files or directories included in the service. + Files map[string]interface{} `json:"files,omitempty"` + + // Scripts contains script definitions for service lifecycle hooks or tasks. + Scripts map[string]interface{} `json:"scripts,omitempty"` +} + +// FoxxServiceObject is the top-level response object for a Foxx service details request. +type FoxxServiceObject struct { + // Common fields for all Foxx services. + CommonFoxxServiceFields + + // Path is the local filesystem path where the service is installed. + Path *string `json:"path,omitempty"` + + // Manifest contains the normalized manifest.json of the service. + Manifest *Manifest `json:"manifest,omitempty"` + + // Options contains optional runtime options defined for the service. + Options map[string]interface{} `json:"options,omitempty"` } diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 339cd53a..4c43a776 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -102,8 +102,8 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op } } -// GetInstalledFoxxService retrieves the list of Foxx services. -func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceObject, error) { +// ListInstalledFoxxServices retrieves the list of Foxx services. +func (c *clientFoxx) ListInstalledFoxxServices(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceListItem, error) { // Ensure the URL starts with a slash urlEndpoint := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx") @@ -122,16 +122,16 @@ func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, switch code := resp.Code(); code { case http.StatusOK: // Try to unmarshal as array first - var result []FoxxServiceObject + var result []FoxxServiceListItem if err := json.Unmarshal(rawResult, &result); err == nil { return result, nil } // If array unmarshaling fails, try as object with result field var objResult struct { - Result []FoxxServiceObject `json:"result"` - Error bool `json:"error"` - Code int `json:"code"` + Result []FoxxServiceListItem `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` } if err := json.Unmarshal(rawResult, &objResult); err == nil { @@ -142,8 +142,38 @@ func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, } // If both fail, return the unmarshal error - return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceObject or object with result field: %s", string(rawResult)) + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) default: return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +// GetInstalledFoxxService retrieves detailed information about a specific Foxx service +func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, mount *string) (FoxxServiceObject, error) { + // Ensure the URL starts with a slash + urlEndpoint := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx", "service") + + // Append query param if needed + if mount == nil || *mount == "" { + return FoxxServiceObject{}, RequiredFieldError("mount") + } + + urlEndpoint += fmt.Sprintf("?mount=%s", url.PathEscape(*mount)) + + // Use json.RawMessage to capture raw response for debugging + var result struct { + shared.ResponseStruct `json:",inline"` + FoxxServiceObject `json:",inline"` + } + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &result) + if err != nil { + return FoxxServiceObject{}, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + return result.FoxxServiceObject, nil + default: + return FoxxServiceObject{}, result.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 2d3452d0..ed4ff432 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -22,6 +22,7 @@ package tests import ( "context" + "fmt" "os" "testing" "time" @@ -82,7 +83,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) } -func Test_GetInstalledFoxxService(t *testing.T) { +func Test_ListInstalledFoxxServices(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx, 30*time.Second) @@ -91,8 +92,8 @@ func Test_GetInstalledFoxxService(t *testing.T) { db, err := client.GetDatabase(ctx, "_system", nil) require.NoError(t, err) - excludeSystem := false - services, err := client.GetInstalledFoxxService(ctx, db.Name(), &excludeSystem) + // excludeSystem := false + services, err := client.ListInstalledFoxxServices(ctx, db.Name(), nil) require.NoError(t, err) require.NotEmpty(t, services) require.GreaterOrEqual(t, len(services), 0) @@ -112,3 +113,22 @@ func Test_GetInstalledFoxxService(t *testing.T) { } }) } + +func Test_GetInstalledFoxxService(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + + mount := "/_api/foxx" + serviceDetails, err := client.GetInstalledFoxxService(ctx, db.Name(), &mount) + require.NoError(t, err) + require.NotEmpty(t, serviceDetails) + servicesJson, err := utils.ToJSONString(serviceDetails) + require.NoError(t, err) + fmt.Printf("serviceDetails after marshalling : %s", servicesJson) + }) +} From 07b3b3b65984d991e592d3f59351077b82821cf9 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Fri, 8 Aug 2025 13:47:06 +0530 Subject: [PATCH 03/41] Foxx: few test scenarios added --- v2/tests/foxx_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index ed4ff432..95768df6 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -22,7 +22,6 @@ package tests import ( "context" - "fmt" "os" "testing" "time" @@ -127,8 +126,12 @@ func Test_GetInstalledFoxxService(t *testing.T) { serviceDetails, err := client.GetInstalledFoxxService(ctx, db.Name(), &mount) require.NoError(t, err) require.NotEmpty(t, serviceDetails) - servicesJson, err := utils.ToJSONString(serviceDetails) - require.NoError(t, err) - fmt.Printf("serviceDetails after marshalling : %s", servicesJson) + require.NotNil(t, serviceDetails.Mount) + require.NotNil(t, serviceDetails.Name) + require.NotNil(t, serviceDetails.Version) + require.NotNil(t, serviceDetails.Development) + require.NotNil(t, serviceDetails.Path) + require.NotNil(t, serviceDetails.Legacy) + require.NotNil(t, serviceDetails.Manifest) }) } From c479892e1fa261d5ce96f0d46858a5d15c0db7f2 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 12 Aug 2025 22:12:09 +0530 Subject: [PATCH 04/41] Done response code and testcase chages for install and uninstall fox service --- v2/arangodb/client_foxx.go | 18 +++++-- v2/arangodb/client_foxx_impl.go | 50 ++++++++++++++----- v2/tests/foxx_test.go | 88 +++++++++++++++++---------------- 3 files changed, 96 insertions(+), 60 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 2572c9ad..2a8e5dec 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -73,8 +73,10 @@ func (c *InstallFoxxServiceRequest) modifyRequest(r connection.Request) error { } r.AddHeader(connection.ContentType, "application/zip") - r.AddQuery("mount", *c.Mount) - + if c.Mount != nil && *c.Mount != "" { + mount := *c.Mount + r.AddQuery("mount", mount) + } return nil } @@ -83,10 +85,16 @@ func (c *UninstallFoxxServiceRequest) modifyRequest(r connection.Request) error return nil } - r.AddQuery("mount", *c.Mount) - r.AddQuery("teardown", strconv.FormatBool(*c.Teardown)) - return nil + if c.Mount != nil && *c.Mount != "" { + mount := *c.Mount + r.AddQuery("mount", mount) + } + if c.Teardown != nil { + r.AddQuery("teardown", strconv.FormatBool(*c.Teardown)) + } + + return nil } type CommonFoxxServiceFields struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 4c43a776..64c64207 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -44,6 +44,33 @@ func newClientFoxx(client *client) *clientFoxx { } } +func (c *clientFoxx) url(dbName string, pathSegments []string, queryParams map[string]interface{}) string { + + base := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx") + for _, seg := range pathSegments { + base = fmt.Sprintf("%s/%s", base, url.PathEscape(seg)) + } + + if len(queryParams) > 0 { + q := url.Values{} + for k, v := range queryParams { + switch val := v.(type) { + case string: + q.Set(k, val) + case bool: + q.Set(k, fmt.Sprintf("%t", val)) + case int, int64, float64: + q.Set(k, fmt.Sprintf("%v", val)) + default: + // skip unsupported types or handle as needed + } + fmt.Printf("Valueeeeeee of Q is %+v", q) + } + base = fmt.Sprintf("%s?%s", base, q.Encode()) + } + return base +} + // InstallFoxxService installs a new service at a given mount path. func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxCreateOptions) error { @@ -68,7 +95,7 @@ func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipF } switch code := resp.Code(); code { - case http.StatusOK: + case http.StatusCreated: return nil default: return response.AsArangoErrorWithCode(code) @@ -78,6 +105,7 @@ func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipF // UninstallFoxxService uninstalls service at a given mount path. func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, opts *FoxxDeleteOptions) error { + url := connection.NewUrl("_db", dbName, "_api/foxx/service") var response struct { @@ -89,13 +117,13 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op request.FoxxDeleteOptions = *opts } - resp, err := connection.CallPost(ctx, c.client.connection, url, &response, nil, request.modifyRequest) + resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, request.modifyRequest) if err != nil { return errors.WithStack(err) } switch code := resp.Code(); code { - case http.StatusOK: + case http.StatusNoContent: return nil default: return response.AsArangoErrorWithCode(code) @@ -104,14 +132,13 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op // ListInstalledFoxxServices retrieves the list of Foxx services. func (c *clientFoxx) ListInstalledFoxxServices(ctx context.Context, dbName string, excludeSystem *bool) ([]FoxxServiceListItem, error) { - // Ensure the URL starts with a slash - urlEndpoint := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx") - - // Append query param if needed + query := map[string]interface{}{} + // query params if excludeSystem != nil { - urlEndpoint += fmt.Sprintf("?excludeSystem=%t", *excludeSystem) + query["excludeSystem"] = *excludeSystem } + urlEndpoint := c.url(dbName, nil, query) // Use json.RawMessage to capture raw response for debugging var rawResult json.RawMessage resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) @@ -150,15 +177,14 @@ func (c *clientFoxx) ListInstalledFoxxServices(ctx context.Context, dbName strin // GetInstalledFoxxService retrieves detailed information about a specific Foxx service func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, mount *string) (FoxxServiceObject, error) { - // Ensure the URL starts with a slash - urlEndpoint := connection.NewUrl("_db", url.PathEscape(dbName), "_api", "foxx", "service") - // Append query param if needed if mount == nil || *mount == "" { return FoxxServiceObject{}, RequiredFieldError("mount") } - urlEndpoint += fmt.Sprintf("?mount=%s", url.PathEscape(*mount)) + urlEndpoint := c.url(dbName, []string{"service"}, map[string]interface{}{ + "mount": *mount, + }) // Use json.RawMessage to capture raw response for debugging var result struct { diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 95768df6..c4159273 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -29,54 +29,56 @@ import ( "github.com/stretchr/testify/require" "github.com/arangodb/go-driver/v2/arangodb" - "github.com/arangodb/go-driver/v2/connection" "github.com/arangodb/go-driver/v2/utils" ) func Test_FoxxItzpapalotlService(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { - WithDatabase(t, client, nil, func(db arangodb.Database) { - t.Run("Install and uninstall Foxx", func(t *testing.T) { - withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { - - if os.Getenv("TEST_CONNECTION") == "vst" { - skipBelowVersion(client, ctx, "3.6", t) - } - - // /tmp/resources/ directory is provided by .travis.yml - zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" - if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { - // Test works only via travis pipeline unless the above file exists locally - t.Skipf("file %s does not exist", zipFilePath) - } - - timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) - mountName := "test" - options := &arangodb.FoxxCreateOptions{ - Mount: utils.NewType[string]("/" + mountName), - } - err := client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) - cancel() - require.NoError(t, err) - - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) - resp, err := connection.CallGet(ctx, client.Connection(), "_db/_system/"+mountName+"/random", nil, nil, nil) - require.NotNil(t, resp) - - value, ok := resp, true - require.Equal(t, true, ok) - require.NotEmpty(t, value) - cancel() - - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) - deleteOptions := &arangodb.FoxxDeleteOptions{ - Mount: utils.NewType[string]("/" + mountName), - Teardown: utils.NewType[bool](true), - } - err = client.UninstallFoxxService(timeoutCtx, db.Name(), deleteOptions) - cancel() - require.NoError(t, err) - }) + t.Run("Install and uninstall Foxx", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + if os.Getenv("TEST_CONNECTION") == "vst" { + skipBelowVersion(client, ctx, "3.6", t) + } + + // /tmp/resources/ directory is provided by .travis.yml + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { + // Test works only via travis pipeline unless the above file exists locally + t.Skipf("file %s does not exist", zipFilePath) + } + + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + mountName := "test" + options := &arangodb.FoxxCreateOptions{ + Mount: utils.NewType[string]("/" + mountName), + } + err = client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) + cancel() + require.NoError(t, err) + + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) + connection := client.Connection() + req, err := connection.NewRequest("GET", "_db/"+db.Name()+"/"+mountName+"/random") + require.NoError(t, err) + resp, err := connection.Do(timeoutCtx, req, nil) + require.NoError(t, err) + require.NotNil(t, resp) + + value, ok := resp, true + require.Equal(t, true, ok) + require.NotEmpty(t, value) + cancel() + + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) + deleteOptions := &arangodb.FoxxDeleteOptions{ + Mount: utils.NewType[string]("/" + mountName), + Teardown: utils.NewType[bool](true), + } + err = client.UninstallFoxxService(timeoutCtx, db.Name(), deleteOptions) + cancel() + require.NoError(t, err) }) }) }) From b287806f9d261abc0d0d9d946e9cb4769ed4848e Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 13 Aug 2025 12:08:11 +0530 Subject: [PATCH 05/41] Add replace foxx service endpoint --- v2/arangodb/client_foxx.go | 13 +++++++----- v2/arangodb/client_foxx_impl.go | 37 ++++++++++++++++++++++++++++++--- v2/tests/foxx_test.go | 7 ++++++- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 2a8e5dec..d4685ea8 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -33,7 +33,7 @@ type ClientFoxx interface { type ClientFoxxService interface { // InstallFoxxService installs a new service at a given mount path. - InstallFoxxService(ctx context.Context, dbName string, zipFile string, options *FoxxCreateOptions) error + InstallFoxxService(ctx context.Context, dbName string, zipFile string, options *FoxxDeploymentOptions) error // UninstallFoxxService uninstalls service at a given mount path. UninstallFoxxService(ctx context.Context, dbName string, options *FoxxDeleteOptions) error // ListInstalledFoxxServices retrieves the list of Foxx services installed in the specified database. @@ -47,9 +47,12 @@ type ClientFoxxService interface { // The returned FoxxServiceObject contains the full metadata and configuration details // for the specified service. GetInstalledFoxxService(ctx context.Context, dbName string, mount *string) (FoxxServiceObject, error) + //ReplaceAFoxxService removes the service at the given mount path from the database and file system + //installs the given new service at the same mount path. + ReplaceAFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error } -type FoxxCreateOptions struct { +type FoxxDeploymentOptions struct { Mount *string } @@ -59,15 +62,15 @@ type FoxxDeleteOptions struct { } // ImportDocumentRequest holds Query parameters for /import. -type InstallFoxxServiceRequest struct { - FoxxCreateOptions `json:",inline"` +type DeployFoxxServiceRequest struct { + FoxxDeploymentOptions `json:",inline"` } type UninstallFoxxServiceRequest struct { FoxxDeleteOptions `json:",inline"` } -func (c *InstallFoxxServiceRequest) modifyRequest(r connection.Request) error { +func (c *DeployFoxxServiceRequest) modifyRequest(r connection.Request) error { if c == nil { return nil } diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 64c64207..c6a30134 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -72,16 +72,16 @@ func (c *clientFoxx) url(dbName string, pathSegments []string, queryParams map[s } // InstallFoxxService installs a new service at a given mount path. -func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxCreateOptions) error { +func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error { url := connection.NewUrl("_db", dbName, "_api/foxx") var response struct { shared.ResponseStruct `json:",inline"` } - request := &InstallFoxxServiceRequest{} + request := &DeployFoxxServiceRequest{} if opts != nil { - request.FoxxCreateOptions = *opts + request.FoxxDeploymentOptions = *opts } bytes, err := os.ReadFile(zipFile) @@ -203,3 +203,34 @@ func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, return FoxxServiceObject{}, result.AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) ReplaceAFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error { + + // url := connection.NewUrl("_db", dbName, "_api/foxx/service") + url := c.url(dbName, []string{"service"}, nil) + var response struct { + shared.ResponseStruct `json:",inline"` + } + + request := &DeployFoxxServiceRequest{} + if opts != nil { + request.FoxxDeploymentOptions = *opts + } + + bytes, err := os.ReadFile(zipFile) + if err != nil { + return errors.WithStack(err) + } + + resp, err := connection.CallPut(ctx, c.client.connection, url, &response, bytes, request.modifyRequest) + if err != nil { + return errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return nil + default: + return response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index c4159273..f53fff6a 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -51,7 +51,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) mountName := "test" - options := &arangodb.FoxxCreateOptions{ + options := &arangodb.FoxxDeploymentOptions{ Mount: utils.NewType[string]("/" + mountName), } err = client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) @@ -71,6 +71,11 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.NotEmpty(t, value) cancel() + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Minute*30) + err = client.ReplaceAFoxxService(timeoutCtx, db.Name(), zipFilePath, options) + cancel() + require.NoError(t, err) + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) deleteOptions := &arangodb.FoxxDeleteOptions{ Mount: utils.NewType[string]("/" + mountName), From 1d00dc26e393d417f98adf155bd5247e0e5be11d Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 13 Aug 2025 13:39:39 +0530 Subject: [PATCH 06/41] Add upgrade foxx service endpoint --- v2/arangodb/client_foxx.go | 10 +++++++--- v2/arangodb/client_foxx_impl.go | 33 ++++++++++++++++++++++++++++++++- v2/tests/foxx_test.go | 14 ++++++++++++-- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index d4685ea8..26a869bf 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -47,9 +47,13 @@ type ClientFoxxService interface { // The returned FoxxServiceObject contains the full metadata and configuration details // for the specified service. GetInstalledFoxxService(ctx context.Context, dbName string, mount *string) (FoxxServiceObject, error) - //ReplaceAFoxxService removes the service at the given mount path from the database and file system - //installs the given new service at the same mount path. - ReplaceAFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error + // ReplaceFoxxService removes the service at the given mount path from the database and file system + // and installs the given new service at the same mount path. + ReplaceFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error + // UpgradeFoxxService installs the given new service on top of the service currently installed + // at the specified mount path, retaining the existing service’s configuration and dependencies. + // This should be used only when upgrading to a newer or equivalent version of the same service. + UpgradeFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index c6a30134..898efb6a 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -204,7 +204,7 @@ func (c *clientFoxx) GetInstalledFoxxService(ctx context.Context, dbName string, } } -func (c *clientFoxx) ReplaceAFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error { +func (c *clientFoxx) ReplaceFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error { // url := connection.NewUrl("_db", dbName, "_api/foxx/service") url := c.url(dbName, []string{"service"}, nil) @@ -234,3 +234,34 @@ func (c *clientFoxx) ReplaceAFoxxService(ctx context.Context, dbName string, zip return response.AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) UpgradeFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error { + + // url := connection.NewUrl("_db", dbName, "_api/foxx/service") + url := c.url(dbName, []string{"service"}, nil) + var response struct { + shared.ResponseStruct `json:",inline"` + } + + request := &DeployFoxxServiceRequest{} + if opts != nil { + request.FoxxDeploymentOptions = *opts + } + + bytes, err := os.ReadFile(zipFile) + if err != nil { + return errors.WithStack(err) + } + + resp, err := connection.CallPatch(ctx, c.client.connection, url, &response, bytes, request.modifyRequest) + if err != nil { + return errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusOK: + return nil + default: + return response.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index f53fff6a..58f4f7a9 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -34,7 +34,7 @@ import ( func Test_FoxxItzpapalotlService(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { - t.Run("Install and uninstall Foxx", func(t *testing.T) { + t.Run("Deploy and uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { db, err := client.GetDatabase(ctx, "_system", nil) require.NoError(t, err) @@ -49,6 +49,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { t.Skipf("file %s does not exist", zipFilePath) } + // InstallFoxxService timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) mountName := "test" options := &arangodb.FoxxDeploymentOptions{ @@ -58,6 +59,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { cancel() require.NoError(t, err) + // Try to fetch random name from installed foxx sercice timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) connection := client.Connection() req, err := connection.NewRequest("GET", "_db/"+db.Name()+"/"+mountName+"/random") @@ -71,11 +73,19 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.NotEmpty(t, value) cancel() + // ReplaceFoxxService timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Minute*30) - err = client.ReplaceAFoxxService(timeoutCtx, db.Name(), zipFilePath, options) + err = client.ReplaceFoxxService(timeoutCtx, db.Name(), zipFilePath, options) cancel() require.NoError(t, err) + // UpgradeFoxxService + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Minute*30) + err = client.UpgradeFoxxService(timeoutCtx, db.Name(), zipFilePath, options) + cancel() + require.NoError(t, err) + + // UninstallFoxxService timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) deleteOptions := &arangodb.FoxxDeleteOptions{ Mount: utils.NewType[string]("/" + mountName), From 27c1a10f31bff7291912d46faa09b4af793b295b Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 13 Aug 2025 17:03:17 +0530 Subject: [PATCH 07/41] Add endpoint to fetch configuration for a specified foxx service mount --- v2/arangodb/client_foxx.go | 2 + v2/arangodb/client_foxx_impl.go | 47 ++++++++++++++++++++- v2/tests/foxx_test.go | 75 ++++++++++++++++++++++----------- 3 files changed, 99 insertions(+), 25 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 26a869bf..7142a802 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -54,6 +54,8 @@ type ClientFoxxService interface { // at the specified mount path, retaining the existing service’s configuration and dependencies. // This should be used only when upgrading to a newer or equivalent version of the same service. UpgradeFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error + + GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 898efb6a..3aa16845 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -64,7 +64,6 @@ func (c *clientFoxx) url(dbName string, pathSegments []string, queryParams map[s default: // skip unsupported types or handle as needed } - fmt.Printf("Valueeeeeee of Q is %+v", q) } base = fmt.Sprintf("%s?%s", base, q.Encode()) } @@ -265,3 +264,49 @@ func (c *clientFoxx) UpgradeFoxxService(ctx context.Context, dbName string, zipF return response.AsArangoErrorWithCode(code) } } + +// GetFoxxServiceConfiguration retrieves the configuration for a specific Foxx service. +func (c *clientFoxx) GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + // Try to unmarshal as array first + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + // If array unmarshaling fails, try as object with result field + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + // If both fail, return the unmarshal error + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 58f4f7a9..aa5f8353 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -34,27 +34,31 @@ import ( func Test_FoxxItzpapalotlService(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { - t.Run("Deploy and uninstall Foxx service", func(t *testing.T) { - withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { - db, err := client.GetDatabase(ctx, "_system", nil) - require.NoError(t, err) - if os.Getenv("TEST_CONNECTION") == "vst" { - skipBelowVersion(client, ctx, "3.6", t) - } - // /tmp/resources/ directory is provided by .travis.yml - zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" - if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { - // Test works only via travis pipeline unless the above file exists locally - t.Skipf("file %s does not exist", zipFilePath) - } + ctx := context.Background() + db, err := client.GetDatabase(ctx, "_system", nil) + require.NoError(t, err) + + if os.Getenv("TEST_CONNECTION") == "vst" { + skipBelowVersion(client, ctx, "3.6", t) + } - // InstallFoxxService + // /tmp/resources/ directory is provided by .travis.yml + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { + // Test works only via travis pipeline unless the above file exists locally + t.Skipf("file %s does not exist", zipFilePath) + } + mountName := "test" + options := &arangodb.FoxxDeploymentOptions{ + Mount: utils.NewType[string]("/" + mountName), + } + + // InstallFoxxService + t.Run("Install and verify installed Foxx service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) - mountName := "test" - options := &arangodb.FoxxDeploymentOptions{ - Mount: utils.NewType[string]("/" + mountName), - } + err = client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) cancel() require.NoError(t, err) @@ -72,21 +76,44 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.Equal(t, true, ok) require.NotEmpty(t, value) cancel() + }) + }) - // ReplaceFoxxService - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Minute*30) + // ReplaceFoxxService + t.Run("Replace Foxx service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) err = client.ReplaceFoxxService(timeoutCtx, db.Name(), zipFilePath, options) cancel() require.NoError(t, err) + }) + }) - // UpgradeFoxxService - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Minute*30) + // UpgradeFoxxService + t.Run("Upgrade Foxx service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) err = client.UpgradeFoxxService(timeoutCtx, db.Name(), zipFilePath, options) cancel() require.NoError(t, err) + }) + }) - // UninstallFoxxService - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) + // Foxx Service Configurations + t.Run("Fetch Foxx service Configuration", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.GetFoxxServiceConfiguration(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + + // UninstallFoxxService + t.Run("Uninstall Foxx service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30) deleteOptions := &arangodb.FoxxDeleteOptions{ Mount: utils.NewType[string]("/" + mountName), Teardown: utils.NewType[bool](true), From fc09f69362e8bbd4b64dec83817dbca189f5bd5e Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Wed, 13 Aug 2025 17:12:21 +0530 Subject: [PATCH 08/41] Comments added --- v2/arangodb/client_foxx.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 7142a802..fd091eb1 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -54,7 +54,10 @@ type ClientFoxxService interface { // at the specified mount path, retaining the existing service’s configuration and dependencies. // This should be used only when upgrading to a newer or equivalent version of the same service. UpgradeFoxxService(ctx context.Context, dbName string, zipFile string, opts *FoxxDeploymentOptions) error - + // GetFoxxServiceConfiguration retrieves the configuration values for the Foxx service + // mounted at the specified path in the given database. + // The mount parameter must not be nil or empty. + // Returns a map containing the current configuration key-value pairs. GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) } From d9f5c8eecdc1e181ea7de5c0094eb75826b42467 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 11:54:14 +0530 Subject: [PATCH 09/41] Add endpoint to upload configuration options for a specific foxx service --- v2/arangodb/client_foxx.go | 5 ++++ v2/arangodb/client_foxx_impl.go | 45 +++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 14 ++++++++++ 3 files changed, 64 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index fd091eb1..50336c59 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -59,6 +59,11 @@ type ClientFoxxService interface { // The mount parameter must not be nil or empty. // Returns a map containing the current configuration key-value pairs. GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) + // UpdateFoxxServiceConfiguration updates the configuration of a specific Foxx service. + // If the Foxx service does not allow a particular configuration key, it will appear + // in the response warnings. + // The caller is responsible for validating allowed keys before calling this method. + UpdateFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 3aa16845..eefabbd8 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -310,3 +310,48 @@ func (c *clientFoxx) GetFoxxServiceConfiguration(ctx context.Context, dbName str return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) UpdateFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + + resp, err := connection.CallPatch(ctx, c.client.connection, urlEndpoint, &rawResult, opt) + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + // Try to unmarshal as array first + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + // If array unmarshaling fails, try as object with result field + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + // If both fail, return the unmarshal error + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index aa5f8353..92d5ef46 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -110,6 +110,20 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Update Foxx service Configuration + t.Run("Update Foxx service Configuration", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.UpdateFoxxServiceConfiguration(timeoutCtx, db.Name(), options.Mount, map[string]interface{}{ + "apiKey": "abcdef", + "maxItems": 100, + }) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From 20d31c3f4ff41b4d80818ccc3171799a2c629e78 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 12:52:42 +0530 Subject: [PATCH 10/41] Foxx: Add endpoint for replace foxx service --- v2/arangodb/client_foxx.go | 5 ++++ v2/arangodb/client_foxx_impl.go | 45 +++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 14 ++++++++++ 3 files changed, 64 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 50336c59..bdc1bb09 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -64,6 +64,11 @@ type ClientFoxxService interface { // in the response warnings. // The caller is responsible for validating allowed keys before calling this method. UpdateFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) + // ReplaceFoxxServiceConfiguration replaces the given Foxx service's dependencies entirely. + // If the Foxx service does not allow a particular configuration key, it will appear + // in the response warnings. + // The caller is responsible for validating allowed keys before calling this method. + ReplaceFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index eefabbd8..d48947b5 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -355,3 +355,48 @@ func (c *clientFoxx) UpdateFoxxServiceConfiguration(ctx context.Context, dbName return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) ReplaceFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + + resp, err := connection.CallPut(ctx, c.client.connection, urlEndpoint, &rawResult, opt) + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + // Try to unmarshal as array first + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + // If array unmarshaling fails, try as object with result field + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + // If both fail, return the unmarshal error + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 92d5ef46..f4fabd07 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -124,6 +124,20 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Replace Foxx service Configuration + t.Run("Replace Foxx service Configuration", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.ReplaceFoxxServiceConfiguration(timeoutCtx, db.Name(), options.Mount, map[string]interface{}{ + "apiKey": "xyz987", + "maxItems": 100, + }) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From dc022ed02a0f40710c60462dccb1fdf84084c7f3 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 13:44:31 +0530 Subject: [PATCH 11/41] Foxx: Add endpoint to fetch foxx service dependencies --- v2/arangodb/client_foxx.go | 8 ++++++ v2/arangodb/client_foxx_impl.go | 46 +++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 11 ++++++++ 3 files changed, 65 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index bdc1bb09..6119098a 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -69,6 +69,14 @@ type ClientFoxxService interface { // in the response warnings. // The caller is responsible for validating allowed keys before calling this method. ReplaceFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) + + // GetFoxxServiceDependencies retrieves the configured dependencies for a specific Foxx service. + // Returns: + // A map where each key is a dependency name and the value is an object containing: + // * title: Human-readable title of the dependency + // * mount: Current mount path of the dependency service (if set) + // An error if the request fails or the mount is missing. + GetFoxxServiceDependencies(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index d48947b5..a8caa505 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -400,3 +400,49 @@ func (c *clientFoxx) ReplaceFoxxServiceConfiguration(ctx context.Context, dbName return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +// GetFoxxServiceConfiguration retrieves the configuration for a specific Foxx service. +func (c *clientFoxx) GetFoxxServiceDependencies(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"dependencies"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + // Try to unmarshal as array first + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + // If array unmarshaling fails, try as object with result field + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + // If both fail, return the unmarshal error + return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index f4fabd07..fab20949 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -138,6 +138,17 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Fetch Foxx Service Dependencies + t.Run("Fetch Foxx Service Dependencies", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.GetFoxxServiceDependencies(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From 7712fa2c47c66c37a9b9a083f17388e445ca6eb6 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 14:10:21 +0530 Subject: [PATCH 12/41] Foxx: Add endpoint to update foxx service dependencies --- v2/arangodb/client_foxx.go | 5 + v2/arangodb/client_foxx_impl.go | 171 +++++++------------------------- v2/tests/foxx_test.go | 13 +++ 3 files changed, 54 insertions(+), 135 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 6119098a..8ba1cae6 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -77,6 +77,11 @@ type ClientFoxxService interface { // * mount: Current mount path of the dependency service (if set) // An error if the request fails or the mount is missing. GetFoxxServiceDependencies(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) + // UpdateFoxxServiceDependencies updates the configured dependencies of a specific Foxx service. + // If the Foxx service does not allow a particular dependency key, it will appear + // in the "warnings" field of the response. + // The caller is responsible for ensuring that only allowed dependency keys are provided. + UpdateFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index a8caa505..b825931a 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -265,83 +265,53 @@ func (c *clientFoxx) UpgradeFoxxService(ctx context.Context, dbName string, zipF } } -// GetFoxxServiceConfiguration retrieves the configuration for a specific Foxx service. -func (c *clientFoxx) GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { +func (c *clientFoxx) callFoxxServiceAPI( + ctx context.Context, + dbName string, + mount *string, + path string, // "configuration" or "dependencies" + method string, // "GET", "PATCH", "PUT" + body map[string]interface{}, // nil for GET +) (map[string]interface{}, error) { if mount == nil || *mount == "" { return nil, RequiredFieldError("mount") } - urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ + urlEndpoint := c.url(dbName, []string{path}, map[string]interface{}{ "mount": *mount, }) var rawResult json.RawMessage - - resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) - if err != nil { - return nil, err - } - - switch code := resp.Code(); code { - case http.StatusOK: - // Try to unmarshal as array first - var result map[string]interface{} - if err := json.Unmarshal(rawResult, &result); err == nil { - return result, nil - } - - // If array unmarshaling fails, try as object with result field - var objResult struct { - Result map[string]interface{} `json:"result"` - Error bool `json:"error"` - Code int `json:"code"` - } - - if err := json.Unmarshal(rawResult, &objResult); err == nil { - if objResult.Error { - return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) - } - return objResult.Result, nil - } - - // If both fail, return the unmarshal error - return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + var resp connection.Response + var err error + + switch method { + case http.MethodGet: + resp, err = connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) + case http.MethodPatch: + resp, err = connection.CallPatch(ctx, c.client.connection, urlEndpoint, &rawResult, body) + case http.MethodPut: + resp, err = connection.CallPut(ctx, c.client.connection, urlEndpoint, &rawResult, body) default: - return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + return nil, fmt.Errorf("unsupported HTTP method: %s", method) } -} -func (c *clientFoxx) UpdateFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { - if mount == nil || *mount == "" { - return nil, RequiredFieldError("mount") - } - - urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ - "mount": *mount, - }) - - var rawResult json.RawMessage - - resp, err := connection.CallPatch(ctx, c.client.connection, urlEndpoint, &rawResult, opt) if err != nil { return nil, err } switch code := resp.Code(); code { case http.StatusOK: - // Try to unmarshal as array first var result map[string]interface{} if err := json.Unmarshal(rawResult, &result); err == nil { return result, nil } - // If array unmarshaling fails, try as object with result field var objResult struct { Result map[string]interface{} `json:"result"` Error bool `json:"error"` Code int `json:"code"` } - if err := json.Unmarshal(rawResult, &objResult); err == nil { if objResult.Error { return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) @@ -349,100 +319,31 @@ func (c *clientFoxx) UpdateFoxxServiceConfiguration(ctx context.Context, dbName return objResult.Result, nil } - // If both fail, return the unmarshal error - return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) + return nil, fmt.Errorf( + "cannot unmarshal response into map or object with result field: %s", + string(rawResult), + ) default: return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } -func (c *clientFoxx) ReplaceFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { - if mount == nil || *mount == "" { - return nil, RequiredFieldError("mount") - } - - urlEndpoint := c.url(dbName, []string{"configuration"}, map[string]interface{}{ - "mount": *mount, - }) - - var rawResult json.RawMessage - - resp, err := connection.CallPut(ctx, c.client.connection, urlEndpoint, &rawResult, opt) - if err != nil { - return nil, err - } - - switch code := resp.Code(); code { - case http.StatusOK: - // Try to unmarshal as array first - var result map[string]interface{} - if err := json.Unmarshal(rawResult, &result); err == nil { - return result, nil - } - - // If array unmarshaling fails, try as object with result field - var objResult struct { - Result map[string]interface{} `json:"result"` - Error bool `json:"error"` - Code int `json:"code"` - } +func (c *clientFoxx) GetFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "configuration", http.MethodGet, nil) +} - if err := json.Unmarshal(rawResult, &objResult); err == nil { - if objResult.Error { - return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) - } - return objResult.Result, nil - } +func (c *clientFoxx) UpdateFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "configuration", http.MethodPatch, opt) +} - // If both fail, return the unmarshal error - return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) - default: - return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) - } +func (c *clientFoxx) ReplaceFoxxServiceConfiguration(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "configuration", http.MethodPut, opt) } -// GetFoxxServiceConfiguration retrieves the configuration for a specific Foxx service. func (c *clientFoxx) GetFoxxServiceDependencies(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { - if mount == nil || *mount == "" { - return nil, RequiredFieldError("mount") - } - - urlEndpoint := c.url(dbName, []string{"dependencies"}, map[string]interface{}{ - "mount": *mount, - }) - - var rawResult json.RawMessage - - resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &rawResult) - if err != nil { - return nil, err - } - - switch code := resp.Code(); code { - case http.StatusOK: - // Try to unmarshal as array first - var result map[string]interface{} - if err := json.Unmarshal(rawResult, &result); err == nil { - return result, nil - } - - // If array unmarshaling fails, try as object with result field - var objResult struct { - Result map[string]interface{} `json:"result"` - Error bool `json:"error"` - Code int `json:"code"` - } - - if err := json.Unmarshal(rawResult, &objResult); err == nil { - if objResult.Error { - return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) - } - return objResult.Result, nil - } + return c.callFoxxServiceAPI(ctx, dbName, mount, "dependencies", http.MethodGet, nil) +} - // If both fail, return the unmarshal error - return nil, fmt.Errorf("cannot unmarshal response into []FoxxServiceListItem or object with result field: %s", string(rawResult)) - default: - return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) - } +func (c *clientFoxx) UpdateFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "dependencies", http.MethodPatch, opt) } diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index fab20949..9e2ce7af 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -149,6 +149,19 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Update Foxx Service Dependencies + t.Run("Update Foxx Service Dependencies", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.UpdateFoxxServiceDependencies(timeoutCtx, db.Name(), options.Mount, map[string]interface{}{ + "title": "Auth Service", + }) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From 4aee20a037da82f031e76422f2959639e97f670b Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 15:11:23 +0530 Subject: [PATCH 13/41] Foxx: Add endpoint to replace foxx service dependencies --- v2/arangodb/client_foxx.go | 5 +++++ v2/arangodb/client_foxx_impl.go | 4 ++++ v2/tests/foxx_test.go | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 8ba1cae6..a5bc3fe6 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -82,6 +82,11 @@ type ClientFoxxService interface { // in the "warnings" field of the response. // The caller is responsible for ensuring that only allowed dependency keys are provided. UpdateFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) + // ReplaceFoxxServiceDependencies replaces the given Foxx service's dependencies entirely. + // If the Foxx service does not allow a particular dependency key, it will appear + // in the "warnings" field of the response. + // The caller is responsible for validating allowed keys before calling this method. + ReplaceFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index b825931a..62ebcfe8 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -347,3 +347,7 @@ func (c *clientFoxx) GetFoxxServiceDependencies(ctx context.Context, dbName stri func (c *clientFoxx) UpdateFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { return c.callFoxxServiceAPI(ctx, dbName, mount, "dependencies", http.MethodPatch, opt) } + +func (c *clientFoxx) ReplaceFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "dependencies", http.MethodPut, opt) +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 9e2ce7af..99415955 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -162,6 +162,21 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Replace Foxx Service Dependencies + t.Run("Replace Foxx Service Dependencies", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.ReplaceFoxxServiceDependencies(timeoutCtx, db.Name(), options.Mount, map[string]interface{}{ + "title": "Auth Service", + "description": "Service that handles authentication", + "mount": "/auth-v2", + }) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From 03657388ae7615b178c9890682f2e30f91239ffa Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 14 Aug 2025 17:12:42 +0530 Subject: [PATCH 14/41] Foxx: Add endpoints related to fox service scripts --- v2/arangodb/client_foxx.go | 4 +++ v2/arangodb/client_foxx_impl.go | 49 +++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 25 +++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index a5bc3fe6..505fd028 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -87,6 +87,10 @@ type ClientFoxxService interface { // in the "warnings" field of the response. // The caller is responsible for validating allowed keys before calling this method. ReplaceFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) + // GetFoxxServiceScripts retrieves the scripts associated with a specific Foxx service. + GetFoxxServiceScripts(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) + // RunFoxxServiceScript executes a specific script associated with a Foxx service. + RunFoxxServiceScript(ctx context.Context, dbName string, name string, mount *string, body map[string]interface{}) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 62ebcfe8..2301e978 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -351,3 +351,52 @@ func (c *clientFoxx) UpdateFoxxServiceDependencies(ctx context.Context, dbName s func (c *clientFoxx) ReplaceFoxxServiceDependencies(ctx context.Context, dbName string, mount *string, opt map[string]interface{}) (map[string]interface{}, error) { return c.callFoxxServiceAPI(ctx, dbName, mount, "dependencies", http.MethodPut, opt) } + +func (c *clientFoxx) GetFoxxServiceScripts(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + return c.callFoxxServiceAPI(ctx, dbName, mount, "scripts", http.MethodGet, nil) +} + +func (c *clientFoxx) RunFoxxServiceScript(ctx context.Context, dbName string, name string, mount *string, body map[string]interface{}) (map[string]interface{}, error) { + + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"scripts", name}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &rawResult, body) + + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + return nil, fmt.Errorf( + "cannot unmarshal response into map or object with result field: %s", + string(rawResult), + ) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 99415955..7d0b745e 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -177,6 +177,31 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) + // Fetch Foxx Service Scripts + t.Run("Fetch Foxx Service Scripts", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.GetFoxxServiceScripts(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + + // Run Foxx Service Script + t.Run("Run Foxx Service Script", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + scriptName := "cleanupData" + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + _, err := client.RunFoxxServiceScript(timeoutCtx, db.Name(), scriptName, options.Mount, + map[string]interface{}{ + "cleanupData": "Cleanup Old Data", + }) + cancel() + require.Error(t, err) + }) + }) + // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { From febafd58766e8ebfab94ed83e221a005146f05ca Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 11:14:59 +0530 Subject: [PATCH 15/41] Foxx: Add endpoints for RunFoxServiceScripts Enable and Disable development modes --- v2/arangodb/client_foxx.go | 16 ++++ v2/arangodb/client_foxx_impl.go | 147 ++++++++++++++++++++++++++++++++ v2/tests/foxx_test.go | 42 +++++++++ 3 files changed, 205 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 505fd028..cefaac32 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -91,6 +91,15 @@ type ClientFoxxService interface { GetFoxxServiceScripts(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) // RunFoxxServiceScript executes a specific script associated with a Foxx service. RunFoxxServiceScript(ctx context.Context, dbName string, name string, mount *string, body map[string]interface{}) (map[string]interface{}, error) + // RunFoxxServiceTests executes the test suite of a specific Foxx service + // deployed in an ArangoDB database. + RunFoxxServiceTests(ctx context.Context, dbName string, opt FoxxTestOptions) (map[string]interface{}, error) + // EnableDevelopmentMode enables the development mode for a specific Foxx service. + // Development mode causes the Foxx service to be reloaded from the filesystem and its setup + // script (if present) to be re-executed every time the service handles a request. + EnableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) + // DisableDevelopmentMode disables the development mode for a specific Foxx service. + DisableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) } type FoxxDeploymentOptions struct { @@ -256,3 +265,10 @@ type FoxxServiceObject struct { // Options contains optional runtime options defined for the service. Options map[string]interface{} `json:"options,omitempty"` } + +type FoxxTestOptions struct { + FoxxDeploymentOptions + Reporter *string `json:"reporter,omitempty"` + Idiomatic *bool `json:"idiomatic,omitempty"` + Filter *string `json:"filter,omitempty"` +} diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 2301e978..53e2c56d 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -400,3 +400,150 @@ func (c *clientFoxx) RunFoxxServiceScript(ctx context.Context, dbName string, na return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) RunFoxxServiceTests(ctx context.Context, dbName string, opt FoxxTestOptions) (map[string]interface{}, error) { + + if opt.Mount == nil || *opt.Mount == "" { + return nil, RequiredFieldError("mount") + } + + queryParams := map[string]interface{}{ + "mount": *opt.Mount, + } + + if opt.Reporter != nil { + queryParams["reporter"] = *opt.Reporter + } + if opt.Idiomatic != nil { + queryParams["idiomatic"] = *opt.Idiomatic + } + if opt.Filter != nil { + queryParams["filter"] = *opt.Filter + } + + urlEndpoint := c.url(dbName, []string{"tests"}, queryParams) + + var rawResult json.RawMessage + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &rawResult, nil) + + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + return nil, fmt.Errorf( + "cannot unmarshal response into map or object with result field: %s", + string(rawResult), + ) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} + +func (c *clientFoxx) EnableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"development"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &rawResult, nil) + + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + return nil, fmt.Errorf( + "cannot unmarshal response into map or object with result field: %s", + string(rawResult), + ) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} + +func (c *clientFoxx) DisableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) { + + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"development"}, map[string]interface{}{ + "mount": *mount, + }) + + var rawResult json.RawMessage + resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, &rawResult) + + if err != nil { + return nil, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + var result map[string]interface{} + if err := json.Unmarshal(rawResult, &result); err == nil { + return result, nil + } + + var objResult struct { + Result map[string]interface{} `json:"result"` + Error bool `json:"error"` + Code int `json:"code"` + } + if err := json.Unmarshal(rawResult, &objResult); err == nil { + if objResult.Error { + return nil, fmt.Errorf("ArangoDB API error: code %d", objResult.Code) + } + return objResult.Result, nil + } + + return nil, fmt.Errorf( + "cannot unmarshal response into map or object with result field: %s", + string(rawResult), + ) + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 7d0b745e..850cf20c 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -201,6 +201,48 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.Error(t, err) }) }) + // Test Foxx Service + t.Run("Test Foxx Service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.RunFoxxServiceTests(timeoutCtx, db.Name(), arangodb.FoxxTestOptions{ + FoxxDeploymentOptions: arangodb.FoxxDeploymentOptions{ + Mount: utils.NewType("/" + mountName), + }, + }) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + + // Enable Development Mode For Foxx Service + t.Run("Enable Development Mode For Foxx Service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + // if getTestMode() == string(testModeCluster) { + // t.Skipf("It's a cluster mode") + // } + resp, err := client.EnableDevelopmentMode(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) + + // Disable Development Mode For Foxx Service + t.Run("Disable Development Mode For Foxx Service", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + // if getTestMode() == string(testModeCluster) { + // t.Skipf("It's a cluster mode") + // } + resp, err := client.DisableDevelopmentMode(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) // UninstallFoxxService t.Run("Uninstall Foxx service", func(t *testing.T) { From fd3a75727ad62345b51e8943dd293fa43ef95778 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 16:16:26 +0530 Subject: [PATCH 16/41] Foxx: Add endpoint to get fox service readme content --- v2/arangodb/client_foxx.go | 2 ++ v2/arangodb/client_foxx_impl.go | 31 ++++++++++++++++++ v2/tests/foxx_test.go | 57 ++++++++++++++++----------------- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index cefaac32..7d78624f 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -100,6 +100,8 @@ type ClientFoxxService interface { EnableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) // DisableDevelopmentMode disables the development mode for a specific Foxx service. DisableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) + // GetFoxxServiceReadme retrieves the README file for a specific Foxx service. + GetFoxxServiceReadme(ctx context.Context, dbName string, mount *string) ([]byte, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 53e2c56d..b5fe4ec8 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -547,3 +547,34 @@ func (c *clientFoxx) DisableDevelopmentMode(ctx context.Context, dbName string, return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) GetFoxxServiceReadme(ctx context.Context, dbName string, mount *string) ([]byte, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"readme"}, map[string]interface{}{ + "mount": *mount, + }) + + // Create request + req, err := c.client.Connection().NewRequest(http.MethodGet, urlEndpoint) + if err != nil { + return nil, err + } + var data []byte + // Call Do with nil result (we'll handle body manually) + resp, err := c.client.Connection().Do(ctx, req, &data, http.StatusOK, http.StatusNoContent) + if err != nil { + return nil, err + } + + switch resp.Code() { + case http.StatusOK: + return data, nil + case http.StatusNoContent: + return nil, nil + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(resp.Code()) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 850cf20c..5a856336 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -44,7 +44,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + zipFilePath := "tmp/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) @@ -55,30 +55,32 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // InstallFoxxService - t.Run("Install and verify installed Foxx service", func(t *testing.T) { - withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { - timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) - - err = client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) - cancel() - require.NoError(t, err) + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*5) - // Try to fetch random name from installed foxx sercice - timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) - connection := client.Connection() - req, err := connection.NewRequest("GET", "_db/"+db.Name()+"/"+mountName+"/random") - require.NoError(t, err) - resp, err := connection.Do(timeoutCtx, req, nil) - require.NoError(t, err) - require.NotNil(t, resp) + err = client.InstallFoxxService(timeoutCtx, db.Name(), zipFilePath, options) + cancel() + require.NoError(t, err) - value, ok := resp, true - require.Equal(t, true, ok) - require.NotEmpty(t, value) - cancel() - }) + // UninstallFoxxService + defer client.UninstallFoxxService(context.Background(), db.Name(), &arangodb.FoxxDeleteOptions{ + Mount: utils.NewType[string]("/" + mountName), + Teardown: utils.NewType[bool](true), }) + // Try to fetch random name from installed foxx sercice + timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) + connection := client.Connection() + req, err := connection.NewRequest("GET", "_db/"+db.Name()+"/"+mountName+"/random") + require.NoError(t, err) + resp, err := connection.Do(timeoutCtx, req, nil) + require.NoError(t, err) + require.NotNil(t, resp) + + value, ok := resp, true + require.Equal(t, true, ok) + require.NotEmpty(t, value) + cancel() + // ReplaceFoxxService t.Run("Replace Foxx service", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { @@ -244,17 +246,14 @@ func Test_FoxxItzpapalotlService(t *testing.T) { }) }) - // UninstallFoxxService - t.Run("Uninstall Foxx service", func(t *testing.T) { + // Get Foxx Service Readme + t.Run("Get Foxx Service Readme", func(t *testing.T) { withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { - timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*30) - deleteOptions := &arangodb.FoxxDeleteOptions{ - Mount: utils.NewType[string]("/" + mountName), - Teardown: utils.NewType[bool](true), - } - err = client.UninstallFoxxService(timeoutCtx, db.Name(), deleteOptions) + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + resp, err := client.GetFoxxServiceReadme(timeoutCtx, db.Name(), options.Mount) cancel() require.NoError(t, err) + require.NotNil(t, resp) }) }) }) From 4f04f030919ae4d86fdc711592b8560ad9bc1e24 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 16:18:29 +0530 Subject: [PATCH 17/41] change test case zip file name --- v2/tests/foxx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 5a856336..9251ec39 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -44,7 +44,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - zipFilePath := "tmp/resources/itzpapalotl-v1.2.0.zip" + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) From bee22777cf7049380c0e1ba61a825ad221922ba4 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 17:00:49 +0530 Subject: [PATCH 18/41] Foxx: Add endpoint for swagger fox service --- v2/arangodb/client_foxx.go | 29 +++++++++++++++++++++++++++++ v2/arangodb/client_foxx_impl.go | 27 +++++++++++++++++++++++++++ v2/tests/foxx_test.go | 11 +++++++++++ 3 files changed, 67 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 7d78624f..b5772e82 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -102,6 +102,9 @@ type ClientFoxxService interface { DisableDevelopmentMode(ctx context.Context, dbName string, mount *string) (map[string]interface{}, error) // GetFoxxServiceReadme retrieves the README file for a specific Foxx service. GetFoxxServiceReadme(ctx context.Context, dbName string, mount *string) ([]byte, error) + // GetFoxxServiceSwagger retrieves the Swagger specification + // for a specific Foxx service mounted in the given database. + GetFoxxServiceSwagger(ctx context.Context, dbName string, mount *string) (SwaggerResponse, error) } type FoxxDeploymentOptions struct { @@ -274,3 +277,29 @@ type FoxxTestOptions struct { Idiomatic *bool `json:"idiomatic,omitempty"` Filter *string `json:"filter,omitempty"` } + +// SwaggerInfo contains general metadata about the API, typically displayed +// in tools like Swagger UI. +type SwaggerInfo struct { + // Title is the title of the API. + Title *string `json:"title,omitempty"` + // Description provides a short description of the API. + Description *string `json:"description,omitempty"` + // Version specifies the version of the API. + Version *string `json:"version,omitempty"` + // License provides licensing information for the API. + License map[string]interface{} `json:"license,omitempty"` +} + +// SwaggerResponse represents the root object of a Swagger (OpenAPI 2.0) specification. +// It contains metadata, versioning information, available API paths, and additional details. +type SwaggerResponse struct { + // Swagger specifies the Swagger specification version (e.g., "2.0"). + Swagger *string `json:"swagger,omitempty"` + // BasePath defines the base path on which the API is served, relative to the host. + BasePath *string `json:"basePath,omitempty"` + // Paths holds the available endpoints and their supported operations. + Paths map[string]interface{} `json:"paths,omitempty"` + // Info provides metadata about the API, such as title, version, and license. + Info *SwaggerInfo `json:"info,omitempty"` +} diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index b5fe4ec8..fdea36f4 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -578,3 +578,30 @@ func (c *clientFoxx) GetFoxxServiceReadme(ctx context.Context, dbName string, mo return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(resp.Code()) } } + +func (c *clientFoxx) GetFoxxServiceSwagger(ctx context.Context, dbName string, mount *string) (SwaggerResponse, error) { + if mount == nil || *mount == "" { + return SwaggerResponse{}, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"swagger"}, map[string]interface{}{ + "mount": *mount, + }) + + var result struct { + shared.ResponseStruct `json:",inline"` + SwaggerResponse `json:",inline"` + } + + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &result) + if err != nil { + return SwaggerResponse{}, err + } + + switch code := resp.Code(); code { + case http.StatusOK: + return result.SwaggerResponse, nil + default: + return SwaggerResponse{}, result.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 9251ec39..bd5e4870 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -256,6 +256,17 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.NotNil(t, resp) }) }) + + // Get Foxx Service Swagger + t.Run("Get Foxx Service Swagger ", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.GetFoxxServiceSwagger(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) }) } From 2184029b64f4ababf846be9d8d4880f4d680ff56 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 17:43:40 +0530 Subject: [PATCH 19/41] Foxx: Add endpoint to commit local service state --- v2/arangodb/client_foxx.go | 3 +++ v2/arangodb/client_foxx_impl.go | 25 +++++++++++++++++++++++++ v2/tests/foxx_test.go | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index b5772e82..16059287 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -105,6 +105,9 @@ type ClientFoxxService interface { // GetFoxxServiceSwagger retrieves the Swagger specification // for a specific Foxx service mounted in the given database. GetFoxxServiceSwagger(ctx context.Context, dbName string, mount *string) (SwaggerResponse, error) + // CommitFoxxService commits the local Foxx service state of the Coordinator + // to the database. This can resolve service conflicts between Coordinators. + CommitFoxxService(ctx context.Context, dbName string, replace *bool) error } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index fdea36f4..69b4b597 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -605,3 +605,28 @@ func (c *clientFoxx) GetFoxxServiceSwagger(ctx context.Context, dbName string, m return SwaggerResponse{}, result.AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) CommitFoxxService(ctx context.Context, dbName string, replace *bool) error { + queryParams := make(map[string]interface{}) + if replace != nil { + queryParams["replace"] = *replace + } + + urlEndpoint := c.url(dbName, []string{"commit"}, queryParams) + + var result struct { + shared.ResponseStruct `json:",inline"` + } + + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &result, nil) + if err != nil { + return err + } + + switch code := resp.Code(); code { + case http.StatusNoContent: // 204 expected + return nil + default: + return result.AsArangoErrorWithCode(code) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index bd5e4870..5a36aebe 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -267,6 +267,20 @@ func Test_FoxxItzpapalotlService(t *testing.T) { require.NotNil(t, resp) }) }) + + // Commit Foxx Service + t.Run("Commit Foxx Service ", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + role, err := client.ServerRole(ctx) + require.NoError(t, err) + if role != "Single" { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + err := client.CommitFoxxService(timeoutCtx, db.Name(), utils.NewType(false)) + cancel() + require.NoError(t, err) + } + }) + }) }) } From 2f8ce2e69ec18ebdcc5fd9c6f6b18d2ef4b5b5ae Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 18:35:05 +0530 Subject: [PATCH 20/41] Foxx: Add endpoint to download specific service --- v2/arangodb/client_foxx.go | 4 ++++ v2/arangodb/client_foxx_impl.go | 27 +++++++++++++++++++++++++++ v2/tests/foxx_test.go | 11 +++++++++++ 3 files changed, 42 insertions(+) diff --git a/v2/arangodb/client_foxx.go b/v2/arangodb/client_foxx.go index 16059287..bfbf6d1a 100644 --- a/v2/arangodb/client_foxx.go +++ b/v2/arangodb/client_foxx.go @@ -108,6 +108,10 @@ type ClientFoxxService interface { // CommitFoxxService commits the local Foxx service state of the Coordinator // to the database. This can resolve service conflicts between Coordinators. CommitFoxxService(ctx context.Context, dbName string, replace *bool) error + // DownloadFoxxServiceBundle downloads a zip bundle of the Foxx service directory + // from the specified database and mount point. + // Note: The response is the raw zip data (binary). + DownloadFoxxServiceBundle(ctx context.Context, dbName string, mount *string) ([]byte, error) } type FoxxDeploymentOptions struct { diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 69b4b597..789e9aad 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -630,3 +630,30 @@ func (c *clientFoxx) CommitFoxxService(ctx context.Context, dbName string, repla return result.AsArangoErrorWithCode(code) } } + +func (c *clientFoxx) DownloadFoxxServiceBundle(ctx context.Context, dbName string, mount *string) ([]byte, error) { + if mount == nil || *mount == "" { + return nil, RequiredFieldError("mount") + } + + urlEndpoint := c.url(dbName, []string{"download"}, map[string]interface{}{ + "mount": *mount, + }) + // Create request + req, err := c.client.Connection().NewRequest(http.MethodPost, urlEndpoint) + if err != nil { + return nil, err + } + var data []byte + // Call Do with nil result (we'll handle body manually) + resp, err := c.client.Connection().Do(ctx, req, &data, http.StatusOK, http.StatusNoContent) + if err != nil { + return nil, err + } + switch code := resp.Code(); code { + case http.StatusOK: + return data, nil + default: + return nil, (&shared.ResponseStruct{}).AsArangoErrorWithCode(resp.Code()) + } +} diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 5a36aebe..43676cea 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -281,6 +281,17 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } }) }) + + // Download Foxx Service Bundle + t.Run("Download Foxx Service Bundle ", func(t *testing.T) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, t testing.TB) { + timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Minute*30) + resp, err := client.DownloadFoxxServiceBundle(timeoutCtx, db.Name(), options.Mount) + cancel() + require.NoError(t, err) + require.NotNil(t, resp) + }) + }) }) } From 083f65a4b9efd660635d7db6599083d99975801c Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 19:08:57 +0530 Subject: [PATCH 21/41] changed the zip file path --- v2/tests/foxx_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 43676cea..77c99a37 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -44,7 +44,8 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + // zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + zipFilePath := os.Getenv("HOME") + "/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) From 5f8c73caa19f633024a273f0fa27eb1c5c3f033c Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 18 Aug 2025 21:17:14 +0530 Subject: [PATCH 22/41] reverted the zip file path --- v2/tests/foxx_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 77c99a37..43676cea 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -44,8 +44,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - // zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" - zipFilePath := os.Getenv("HOME") + "/resources/itzpapalotl-v1.2.0.zip" + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) From 2fb200b20f7ed7df184199a4138dd60c1831e3ea Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 12:55:13 +0530 Subject: [PATCH 23/41] changes in config --- .circleci/config.yml | 18 ++++++++++++++++-- v2/arangodb/client_foxx_impl.go | 12 ++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4d433f05..426a91ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,6 +60,11 @@ jobs: default: false steps: - checkout + - run: + name: "Check resources before tests" + command: | + echo "HOME is $HOME" + ls -l $HOME/resources || echo "No resources in $HOME/resources" - run: name: make << parameters.test-to-run >> command: | @@ -68,6 +73,10 @@ jobs: exit 0 fi make << parameters.test-to-run >> + - run: + name: "Check resources inside container" + command: | + docker run --rm -v $HOME/resources:/tmp/resources alpine ls -l /tmp/resources environment: TEST_RESOURCES: "${HOME}/resources/" ARANGODB: << pipeline.parameters.arangodbImage >> @@ -92,6 +101,7 @@ jobs: if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" fi + - run: ls -l $HOME/resources vulncheck: executor: golang-executor @@ -127,13 +137,16 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-cluster + environment: + TEST_RESOURCES: "${HOME}/resources/" - run-integration-tests: name: Test V2 cluster - DB extra features (compression) requires: - download-demo-data test-to-run: run-v2-tests-cluster enable-extra-db-features: true - + environment: + TEST_RESOURCES: "${HOME}/resources/" - run-integration-tests: name: Test V1 single requires: @@ -144,7 +157,8 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-single - + environment: + TEST_RESOURCES: "${HOME}/resources/" # Weekly vulnerability check weekly_vulncheck: jobs: diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 789e9aad..c3bac3f3 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -83,6 +83,10 @@ func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipF request.FoxxDeploymentOptions = *opts } + if _, err := os.Stat(zipFile); os.IsNotExist(err) { + return errors.WithStack(err) + } + bytes, err := os.ReadFile(zipFile) if err != nil { return errors.WithStack(err) @@ -216,6 +220,10 @@ func (c *clientFoxx) ReplaceFoxxService(ctx context.Context, dbName string, zipF request.FoxxDeploymentOptions = *opts } + if _, err := os.Stat(zipFile); os.IsNotExist(err) { + return errors.WithStack(err) + } + bytes, err := os.ReadFile(zipFile) if err != nil { return errors.WithStack(err) @@ -247,6 +255,10 @@ func (c *clientFoxx) UpgradeFoxxService(ctx context.Context, dbName string, zipF request.FoxxDeploymentOptions = *opts } + if _, err := os.Stat(zipFile); os.IsNotExist(err) { + return errors.WithStack(err) + } + bytes, err := os.ReadFile(zipFile) if err != nil { return errors.WithStack(err) From d5bb7fd9e70ab3e71b4f596f9d0e6af845bc3256 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 12:59:10 +0530 Subject: [PATCH 24/41] changes in config file --- .circleci/config.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 426a91ad..a327f39b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,11 +60,6 @@ jobs: default: false steps: - checkout - - run: - name: "Check resources before tests" - command: | - echo "HOME is $HOME" - ls -l $HOME/resources || echo "No resources in $HOME/resources" - run: name: make << parameters.test-to-run >> command: | @@ -73,10 +68,6 @@ jobs: exit 0 fi make << parameters.test-to-run >> - - run: - name: "Check resources inside container" - command: | - docker run --rm -v $HOME/resources:/tmp/resources alpine ls -l /tmp/resources environment: TEST_RESOURCES: "${HOME}/resources/" ARANGODB: << pipeline.parameters.arangodbImage >> From 91546f22a16d2edc43a2a996efecaec0601acec8 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 13:07:57 +0530 Subject: [PATCH 25/41] changes in config file --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a327f39b..b2d6179d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -123,6 +123,7 @@ workflows: requires: - download-demo-data test-to-run: run-tests-cluster + - run-integration-tests: name: Test V2 cluster requires: @@ -130,19 +131,22 @@ workflows: test-to-run: run-v2-tests-cluster environment: TEST_RESOURCES: "${HOME}/resources/" + - run-integration-tests: name: Test V2 cluster - DB extra features (compression) requires: - download-demo-data test-to-run: run-v2-tests-cluster enable-extra-db-features: true - environment: + environment: TEST_RESOURCES: "${HOME}/resources/" + - run-integration-tests: name: Test V1 single requires: - download-demo-data test-to-run: run-tests-single + - run-integration-tests: name: Test V2 single requires: From 35ffe04f988af45ab34370d50626530aad27f7f2 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 13:11:46 +0530 Subject: [PATCH 26/41] reverted workflows in config file and added resources-path --- .circleci/config.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b2d6179d..39aa7e33 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,8 +129,7 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-cluster - environment: - TEST_RESOURCES: "${HOME}/resources/" + resources-path: "${HOME}/resources/" - run-integration-tests: name: Test V2 cluster - DB extra features (compression) @@ -138,8 +137,7 @@ workflows: - download-demo-data test-to-run: run-v2-tests-cluster enable-extra-db-features: true - environment: - TEST_RESOURCES: "${HOME}/resources/" + resources-path: "${HOME}/resources/" - run-integration-tests: name: Test V1 single @@ -152,8 +150,7 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-single - environment: - TEST_RESOURCES: "${HOME}/resources/" + resources-path: "${HOME}/resources/" # Weekly vulnerability check weekly_vulncheck: jobs: From 2dc44ddd00cf5f0e15e6ed15fd639a434531284c Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 13:13:15 +0530 Subject: [PATCH 27/41] reverted workflows in config file --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 39aa7e33..8a111593 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -129,7 +129,6 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-cluster - resources-path: "${HOME}/resources/" - run-integration-tests: name: Test V2 cluster - DB extra features (compression) @@ -137,7 +136,6 @@ workflows: - download-demo-data test-to-run: run-v2-tests-cluster enable-extra-db-features: true - resources-path: "${HOME}/resources/" - run-integration-tests: name: Test V1 single @@ -150,7 +148,6 @@ workflows: requires: - download-demo-data test-to-run: run-v2-tests-single - resources-path: "${HOME}/resources/" # Weekly vulnerability check weekly_vulncheck: jobs: From 6c179c82266d6ff4d77a077198ecef31d7209f33 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 14:17:29 +0530 Subject: [PATCH 28/41] trying to from test file that check temp resources is there or not --- v2/tests/foxx_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 43676cea..d6c654d8 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -44,6 +44,16 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml + zipFilePath1 := "/tmp/resources" + if info, err := os.Stat(zipFilePath1); err != nil { + if os.IsNotExist(err) { + t.Skipf("path %s does not exist", zipFilePath1) + } + t.Fatalf("error checking path %s: %v", zipFilePath1, err) + } else if !info.IsDir() { + t.Skipf("path %s is not a directory", zipFilePath1) + } + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { // Test works only via travis pipeline unless the above file exists locally From 9415385bb40444a683b5c77d5e79b661573e0a42 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 14:51:14 +0530 Subject: [PATCH 29/41] In config file moved download-demo-data above the run-integration-tests --- .circleci/config.yml | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a111593..9e9c122a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,6 +49,26 @@ jobs: environment: GOIMAGE: << pipeline.parameters.goImage >> + download-demo-data: + executor: machine-executor + steps: + - run: mkdir -p $HOME/resources + - run: + name: Download itzpapalotl demo foxx service + command: | + if [ -z "$CIRCLE_PULL_REQUEST" ]; then + echo "This is not a pull request. Skipping..." + exit 0 + fi + if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then + curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" + fi + - run: ls -l $HOME/resources + - persist_to_workspace: + root: ~/ + paths: + - resources + run-integration-tests: executor: machine-executor parameters: @@ -78,22 +98,6 @@ jobs: TEST_DISALLOW_UNKNOWN_FIELDS: false VERBOSE: 1 - download-demo-data: - executor: machine-executor - steps: - - run: mkdir -p $HOME/resources - - run: - name: Download itzpapalotl demo foxx service - command: | - if [ -z "$CIRCLE_PULL_REQUEST" ]; then - echo "This is not a pull request. Skipping..." - exit 0 - fi - if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then - curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" - fi - - run: ls -l $HOME/resources - vulncheck: executor: golang-executor steps: From 883b9add625b4315cbf12aabd79d941039800e08 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 15:17:54 +0530 Subject: [PATCH 30/41] Added echo in config file --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e9c122a..cfd51a2d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,6 +80,10 @@ jobs: default: false steps: - checkout + - attach_workspace: + at: ~/ + - run: echo "TEST_RESOURCES=$TEST_RESOURCES" + - run: ls -l $TEST_RESOURCES - run: name: make << parameters.test-to-run >> command: | From 4a055ae83d562f0a98e70dd2fb1f78e439e2af39 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 15:20:58 +0530 Subject: [PATCH 31/41] identation fixed --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cfd51a2d..c5699451 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,7 +81,7 @@ jobs: steps: - checkout - attach_workspace: - at: ~/ + at: ~/ - run: echo "TEST_RESOURCES=$TEST_RESOURCES" - run: ls -l $TEST_RESOURCES - run: From e2e22aacb7fca2e71aeec33a69d64b7962cb5e6b Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 15:49:47 +0530 Subject: [PATCH 32/41] code changes in config --- .circleci/config.yml | 54 ++++++++++++++++++++++++------------------- v2/tests/foxx_test.go | 12 ++++++---- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c5699451..e28e7b04 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,26 +49,6 @@ jobs: environment: GOIMAGE: << pipeline.parameters.goImage >> - download-demo-data: - executor: machine-executor - steps: - - run: mkdir -p $HOME/resources - - run: - name: Download itzpapalotl demo foxx service - command: | - if [ -z "$CIRCLE_PULL_REQUEST" ]; then - echo "This is not a pull request. Skipping..." - exit 0 - fi - if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then - curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" - fi - - run: ls -l $HOME/resources - - persist_to_workspace: - root: ~/ - paths: - - resources - run-integration-tests: executor: machine-executor parameters: @@ -82,6 +62,14 @@ jobs: - checkout - attach_workspace: at: ~/ + - run: + name: Create resources symlink + command: | + if [ -d "${HOME}/resources" ]; then + sudo mkdir -p /tmp/resources + sudo cp -r ${HOME}/resources/* /tmp/resources/ + ls -la /tmp/resources/ + fi - run: echo "TEST_RESOURCES=$TEST_RESOURCES" - run: ls -l $TEST_RESOURCES - run: @@ -102,6 +90,26 @@ jobs: TEST_DISALLOW_UNKNOWN_FIELDS: false VERBOSE: 1 + download-demo-data: + executor: machine-executor + steps: + - run: mkdir -p $HOME/resources + - run: + name: Download itzpapalotl demo foxx service + command: | + if [ -z "$CIRCLE_PULL_REQUEST" ]; then + echo "This is not a pull request. Skipping..." + exit 0 + fi + if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then + curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" + fi + - run: ls -l $HOME/resources + - persist_to_workspace: + root: ~/ + paths: + - resources + vulncheck: executor: golang-executor steps: @@ -131,13 +139,11 @@ workflows: requires: - download-demo-data test-to-run: run-tests-cluster - - run-integration-tests: name: Test V2 cluster requires: - download-demo-data test-to-run: run-v2-tests-cluster - - run-integration-tests: name: Test V2 cluster - DB extra features (compression) requires: @@ -150,12 +156,12 @@ workflows: requires: - download-demo-data test-to-run: run-tests-single - - run-integration-tests: name: Test V2 single requires: - download-demo-data test-to-run: run-v2-tests-single + # Weekly vulnerability check weekly_vulncheck: jobs: @@ -168,4 +174,4 @@ workflows: cron: 0 6 * * 1 filters: branches: - only: master + only: master \ No newline at end of file diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index d6c654d8..7d9a4ab7 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -23,6 +23,7 @@ package tests import ( "context" "os" + "path/filepath" "testing" "time" @@ -44,19 +45,22 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - zipFilePath1 := "/tmp/resources" + zipFilePath1 := os.Getenv("TEST_RESOURCES") + if zipFilePath1 == "" { + zipFilePath1 = "/tmp/resources" // fallback for local testing + } + if info, err := os.Stat(zipFilePath1); err != nil { if os.IsNotExist(err) { t.Skipf("path %s does not exist", zipFilePath1) } t.Fatalf("error checking path %s: %v", zipFilePath1, err) } else if !info.IsDir() { - t.Skipf("path %s is not a directory", zipFilePath1) + t.Skipf("path %s is not directory", zipFilePath1) } - zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" + zipFilePath := filepath.Join(zipFilePath1, "itzpapalotl-v1.2.0.zip") if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { - // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) } mountName := "test" From 735b2f550c780245070a8aba1ba5543548d08e16 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 16:04:19 +0530 Subject: [PATCH 33/41] code changes in config --- .circleci/config.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e28e7b04..543a77b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,18 +60,40 @@ jobs: default: false steps: - checkout + - run: + name: Debug - Check if this is a PR + command: | + echo "CIRCLE_PULL_REQUEST: $CIRCLE_PULL_REQUEST" + echo "CIRCLE_PR_NUMBER: $CIRCLE_PR_NUMBER" - attach_workspace: at: ~/ + - run: + name: Debug - Check workspace contents + command: | + echo "Contents of HOME directory:" + ls -la $HOME/ + echo "Looking for resources directory:" + find $HOME -name "resources" -type d 2>/dev/null || echo "No resources directory found" - run: name: Create resources symlink command: | if [ -d "${HOME}/resources" ]; then + echo "Found ${HOME}/resources, copying to /tmp/resources" sudo mkdir -p /tmp/resources sudo cp -r ${HOME}/resources/* /tmp/resources/ ls -la /tmp/resources/ + else + echo "Directory ${HOME}/resources does not exist" + echo "Creating empty /tmp/resources for fallback" + sudo mkdir -p /tmp/resources fi - run: echo "TEST_RESOURCES=$TEST_RESOURCES" - - run: ls -l $TEST_RESOURCES + - run: | + if [ -d "$TEST_RESOURCES" ]; then + ls -l $TEST_RESOURCES + else + echo "TEST_RESOURCES directory $TEST_RESOURCES does not exist" + fi - run: name: make << parameters.test-to-run >> command: | From 83fe7a4f2d9445a3a2e373a010a618e3814fcd97 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 16:11:07 +0530 Subject: [PATCH 34/41] reverted config changes --- .circleci/config.yml | 39 --------------------------------------- v2/tests/foxx_test.go | 18 ++---------------- 2 files changed, 2 insertions(+), 55 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 543a77b4..d7f746e1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,40 +60,6 @@ jobs: default: false steps: - checkout - - run: - name: Debug - Check if this is a PR - command: | - echo "CIRCLE_PULL_REQUEST: $CIRCLE_PULL_REQUEST" - echo "CIRCLE_PR_NUMBER: $CIRCLE_PR_NUMBER" - - attach_workspace: - at: ~/ - - run: - name: Debug - Check workspace contents - command: | - echo "Contents of HOME directory:" - ls -la $HOME/ - echo "Looking for resources directory:" - find $HOME -name "resources" -type d 2>/dev/null || echo "No resources directory found" - - run: - name: Create resources symlink - command: | - if [ -d "${HOME}/resources" ]; then - echo "Found ${HOME}/resources, copying to /tmp/resources" - sudo mkdir -p /tmp/resources - sudo cp -r ${HOME}/resources/* /tmp/resources/ - ls -la /tmp/resources/ - else - echo "Directory ${HOME}/resources does not exist" - echo "Creating empty /tmp/resources for fallback" - sudo mkdir -p /tmp/resources - fi - - run: echo "TEST_RESOURCES=$TEST_RESOURCES" - - run: | - if [ -d "$TEST_RESOURCES" ]; then - ls -l $TEST_RESOURCES - else - echo "TEST_RESOURCES directory $TEST_RESOURCES does not exist" - fi - run: name: make << parameters.test-to-run >> command: | @@ -126,11 +92,6 @@ jobs: if ! [ -f "$HOME/resources/itzpapalotl-v1.2.0.zip" ]; then curl -L0 -o $HOME/resources/itzpapalotl-v1.2.0.zip "https://github.com/arangodb-foxx/demo-itzpapalotl/archive/v1.2.0.zip" fi - - run: ls -l $HOME/resources - - persist_to_workspace: - root: ~/ - paths: - - resources vulncheck: executor: golang-executor diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 7d9a4ab7..43676cea 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -23,7 +23,6 @@ package tests import ( "context" "os" - "path/filepath" "testing" "time" @@ -45,22 +44,9 @@ func Test_FoxxItzpapalotlService(t *testing.T) { } // /tmp/resources/ directory is provided by .travis.yml - zipFilePath1 := os.Getenv("TEST_RESOURCES") - if zipFilePath1 == "" { - zipFilePath1 = "/tmp/resources" // fallback for local testing - } - - if info, err := os.Stat(zipFilePath1); err != nil { - if os.IsNotExist(err) { - t.Skipf("path %s does not exist", zipFilePath1) - } - t.Fatalf("error checking path %s: %v", zipFilePath1, err) - } else if !info.IsDir() { - t.Skipf("path %s is not directory", zipFilePath1) - } - - zipFilePath := filepath.Join(zipFilePath1, "itzpapalotl-v1.2.0.zip") + zipFilePath := "/tmp/resources/itzpapalotl-v1.2.0.zip" if _, err := os.Stat(zipFilePath); os.IsNotExist(err) { + // Test works only via travis pipeline unless the above file exists locally t.Skipf("file %s does not exist", zipFilePath) } mountName := "test" From 63c4d1448a4164089e5cb85b13d2ac00b7e2c1ad Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 19 Aug 2025 19:19:17 +0530 Subject: [PATCH 35/41] tag added in changelog --- v2/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/v2/CHANGELOG.md b/v2/CHANGELOG.md index af55f645..cfd07539 100644 --- a/v2/CHANGELOG.md +++ b/v2/CHANGELOG.md @@ -5,6 +5,7 @@ - Add missing endpoints from collections to v2 - Add missing endpoints from query to v2 - Add SSO auth token implementation +- Add missing endpoints from foxx to v2 ## [2.1.3](https://github.com/arangodb/go-driver/tree/v2.1.3) (2025-02-21) - Switch to Go 1.22.11 From 1a0a70763b78ea179e93eb4be5e64862240edf90 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 17:22:43 +0530 Subject: [PATCH 36/41] Update v2/tests/foxx_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- v2/tests/foxx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/tests/foxx_test.go b/v2/tests/foxx_test.go index 43676cea..e8a71c95 100644 --- a/v2/tests/foxx_test.go +++ b/v2/tests/foxx_test.go @@ -67,7 +67,7 @@ func Test_FoxxItzpapalotlService(t *testing.T) { Teardown: utils.NewType[bool](true), }) - // Try to fetch random name from installed foxx sercice + // Try to fetch random name from installed foxx service timeoutCtx, cancel = context.WithTimeout(context.Background(), time.Second*30) connection := client.Connection() req, err := connection.NewRequest("GET", "_db/"+db.Name()+"/"+mountName+"/random") From eb645aab58ba2807e96790ac9f505593926616c6 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 19:08:43 +0530 Subject: [PATCH 37/41] ci: trigger CircleCI build From fc68d43f9ab6a184cfa2f6ea0ab9d2c4375e780a Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 20:06:07 +0530 Subject: [PATCH 38/41] Addressed copilot commets --- v2/arangodb/client_foxx_impl.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index c3bac3f3..54068563 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -98,7 +98,7 @@ func (c *clientFoxx) InstallFoxxService(ctx context.Context, dbName string, zipF } switch code := resp.Code(); code { - case http.StatusCreated: + case http.StatusCreated: // Foxx install returns 201 Created as per ArangoDB API return nil default: return response.AsArangoErrorWithCode(code) @@ -120,6 +120,7 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op request.FoxxDeleteOptions = *opts } + // UninstallFoxxService removes a Foxx service using DELETE as per ArangoDB API resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, request.modifyRequest) if err != nil { return errors.WithStack(err) From 20e8b01e3348988d0735dbf24f9216a5e83de809 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 20:10:36 +0530 Subject: [PATCH 39/41] Addressed copilot comment --- v2/arangodb/client_foxx_impl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 54068563..589e511b 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -119,7 +119,6 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op if opts != nil { request.FoxxDeleteOptions = *opts } - // UninstallFoxxService removes a Foxx service using DELETE as per ArangoDB API resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, request.modifyRequest) if err != nil { From 2ebc26cbc994c6871ca57ed5b95b7d53980b0604 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 20:15:31 +0530 Subject: [PATCH 40/41] Addressed copilot comment for uninstall --- v2/arangodb/client_foxx_impl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 589e511b..8a00aefe 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -119,7 +119,7 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op if opts != nil { request.FoxxDeleteOptions = *opts } - // UninstallFoxxService removes a Foxx service using DELETE as per ArangoDB API + // per ArangoDB docs (Foxx uninstall → DELETE /_db/{db}/_api/foxx/service). resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, request.modifyRequest) if err != nil { return errors.WithStack(err) From 7c75e4877758fb1536ea1c1dce987d5b1bd77fbd Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 25 Aug 2025 20:18:29 +0530 Subject: [PATCH 41/41] Addressed copilot comment for uninstall --- v2/arangodb/client_foxx_impl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/arangodb/client_foxx_impl.go b/v2/arangodb/client_foxx_impl.go index 8a00aefe..d6604d5d 100644 --- a/v2/arangodb/client_foxx_impl.go +++ b/v2/arangodb/client_foxx_impl.go @@ -119,7 +119,7 @@ func (c *clientFoxx) UninstallFoxxService(ctx context.Context, dbName string, op if opts != nil { request.FoxxDeleteOptions = *opts } - // per ArangoDB docs (Foxx uninstall → DELETE /_db/{db}/_api/foxx/service). + // As per ArangoDB docs (Foxx uninstall → DELETE /_db/{db}/_api/foxx/service). resp, err := connection.CallDelete(ctx, c.client.connection, url, &response, request.modifyRequest) if err != nil { return errors.WithStack(err)