Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions v2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Add missing endpoints from cluster to v2
- Add missing endpoints from security to v2
- Add missing endpoints from authentication to v2
- Add missing endpoints from general-request-handling to v2

## [2.1.5](https://github.com/arangodb/go-driver/tree/v2.1.5) (2025-08-31)
- Add tasks endpoints to v2
Expand Down
9 changes: 7 additions & 2 deletions v2/arangodb/client_admin_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,19 @@ func (c *clientAdmin) GetSystemTime(ctx context.Context, dbName string) (float64

// GetServerStatus returns status information about the server
func (c *clientAdmin) GetServerStatus(ctx context.Context, dbName string) (ServerStatusResponse, error) {
url := connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "status")
var endPoint string
if dbName == "" {
endPoint = connection.NewUrl("_admin", "status")
} else {
endPoint = connection.NewUrl("_db", url.PathEscape(dbName), "_admin", "status")
}

var response struct {
shared.ResponseStruct `json:",inline"`
ServerStatusResponse `json:",inline"`
}

resp, err := connection.CallGet(ctx, c.client.connection, url, &response)
resp, err := connection.CallGet(ctx, c.client.connection, endPoint, &response)
if err != nil {
return ServerStatusResponse{}, errors.WithStack(err)
}
Expand Down
4 changes: 4 additions & 0 deletions v2/arangodb/client_server_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ type ClientServerInfo interface {
// ServerID Gets the ID of this server in the cluster.
// An error is returned when calling this to a server that is not part of a cluster.
ServerID(ctx context.Context) (string, error)

// HandleAdminVersion retrieves the ArangoDB server version information
// This endpoint is an alias for `GET /_api/version`.
HandleAdminVersion(ctx context.Context, opts *GetVersionOptions) (VersionInfo, error)
}

// VersionInfo describes the version of a database server.
Expand Down
29 changes: 29 additions & 0 deletions v2/arangodb/client_server_info_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,32 @@ func (o *GetVersionOptions) modifyRequest(r connection.Request) error {
}
return nil
}

// HandleAdminVersion retrieves the ArangoDB server version information
// This endpoint is an alias for `GET /_api/version`.
func (c clientServerInfo) HandleAdminVersion(ctx context.Context, opts *GetVersionOptions) (VersionInfo, error) {
url := connection.NewUrl("_admin", "version")

var response struct {
shared.ResponseStruct `json:",inline"`
VersionInfo
}

// Always provide a non-nil modifier
modifier := func(r connection.Request) error { return nil }
if opts != nil {
modifier = opts.modifyRequest
}

resp, err := connection.CallGet(ctx, c.client.connection, url, &response, modifier)
if err != nil {
return VersionInfo{}, errors.WithStack(err)
}

switch code := resp.Code(); code {
case http.StatusOK:
return response.VersionInfo, nil
default:
return VersionInfo{}, response.AsArangoErrorWithCode(code)
}
}
49 changes: 42 additions & 7 deletions v2/tests/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,26 @@ func Test_GetSystemTime(t *testing.T) {

func Test_GetServerStatus(t *testing.T) {
Wrap(t, func(t *testing.T, client arangodb.Client) {
withContextT(t, time.Minute, func(ctx context.Context, t testing.TB) {
db, err := client.GetDatabase(context.Background(), "_system", nil)
require.NoError(t, err)
require.NotEmpty(t, db)
withContextT(t, time.Minute, func(ctx context.Context, tb testing.TB) {
t.Run("WithoutDBName", func(t *testing.T) {
resp, err := client.GetServerStatus(context.Background(), "")
require.NoError(t, err)
require.NotEmpty(t, resp)
})
t.Run("WithDBName", func(t *testing.T) {
db, err := client.GetDatabase(context.Background(), "_system", nil)
require.NoError(t, err)
require.NotEmpty(t, db)

resp, err := client.GetServerStatus(context.Background(), db.Name())
require.NoError(t, err)
require.NotEmpty(t, resp)
resp, err := client.GetServerStatus(context.Background(), db.Name())
require.NoError(t, err)
require.NotEmpty(t, resp)
})
t.Run("InvalidDBName", func(t *testing.T) {
_, err := client.GetServerStatus(context.Background(), "invalid/db/name")
t.Logf("error :%v\n", err)
require.Error(t, err)
})
})
})
}
Expand Down Expand Up @@ -555,3 +567,26 @@ func validateJWTSecretsResponse(t testing.TB, resp arangodb.JWTSecretsResult, op

t.Logf("%s JWT secrets validation completed successfully with %d total secrets", operation, len(resp.Passive)+1)
}
func Test_HandleAdminVersion(t *testing.T) {
Wrap(t, func(t *testing.T, client arangodb.Client) {
withContextT(t, time.Minute, func(ctx context.Context, tb testing.TB) {
t.Run("With Options", func(t *testing.T) {
resp, err := client.HandleAdminVersion(context.Background(), &arangodb.GetVersionOptions{
Details: utils.NewType(true),
})
require.NoError(t, err)
require.NotEmpty(t, resp.Version)
require.NotEmpty(t, resp.Server)
require.NotEmpty(t, resp.License)
require.NotEmpty(t, resp.Details)
})
t.Run("Without options", func(t *testing.T) {
resp, err := client.HandleAdminVersion(context.Background(), nil)
require.NoError(t, err)
require.NotEmpty(t, resp.Version)
require.NotEmpty(t, resp.Server)
require.NotEmpty(t, resp.License)
})
})
})
}
78 changes: 58 additions & 20 deletions v2/tests/client_access_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"testing"
"time"

"github.com/arangodb/go-driver/v2/arangodb"
"github.com/arangodb/go-driver/v2/arangodb/shared"
"github.com/arangodb/go-driver/v2/utils"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)

Expand All @@ -44,23 +44,41 @@ func Test_AccessTokens(t *testing.T) {
user := "root"

t.Run("Create Access Token With All valid data", func(t *testing.T) {
tokenName := fmt.Sprintf("Token-%d-%d", time.Now().UnixNano(), rand.Int())
t.Logf("Create Access Token With All valid data - Creating token with name: %s\n", tokenName)
req := arangodb.AccessTokenRequest{
Name: utils.NewType(tokenName),
ValidUntil: utils.NewType(expiresAt),
}
var err error
resp, err := client.CreateAccessToken(ctx, &user, req)
maxRetries := 3

for i := 0; i < maxRetries; i++ {
tokenName := fmt.Sprintf("Token-%s", uuid.New().String())
cleanupToken(ctx, t, client, user, tokenName)

req := arangodb.AccessTokenRequest{
Name: utils.NewType(tokenName),
ValidUntil: utils.NewType(expiresAt),
}

resp, err := client.CreateAccessToken(ctx, &user, req)
if err == nil {
tokenResp = &resp
require.NotNil(t, tokenResp)
require.NotNil(t, tokenResp.Id)
require.NotNil(t, tokenResp.Token)
require.NotNil(t, tokenResp.Fingerprint)
require.Equal(t, tokenName, *tokenResp.Name)
require.Equal(t, true, *tokenResp.Active)
require.Equal(t, expiresAt, *tokenResp.ValidUntil)
break // success
}

// if conflict, retry; else fail immediately
var arangoErr shared.ArangoError
if errors.As(err, &arangoErr) && arangoErr.Code == 409 {
t.Logf("Conflict detected, retrying token creation... attempt %d\n", i+1)
continue
} else {
break
}
}
require.NoError(t, err)
require.NotNil(t, resp)
tokenResp = &resp
require.NotNil(t, tokenResp.Id)
require.NotNil(t, tokenResp.Token)
require.NotNil(t, tokenResp.Fingerprint)
require.Equal(t, tokenName, *tokenResp.Name)
require.Equal(t, true, *tokenResp.Active)
require.Equal(t, expiresAt, *tokenResp.ValidUntil)
})

t.Run("Get All Access Tokens", func(t *testing.T) {
Expand All @@ -86,7 +104,7 @@ func Test_AccessTokens(t *testing.T) {
})

t.Run("Client try to create duplicate access token name", func(t *testing.T) {
if tokenResp.Name == nil {
if tokenResp == nil || tokenResp.Name == nil {
t.Skip("Skipping delete test because token creation failed")
}
t.Logf("Client try to create duplicate access token name - token name: %s\n", *tokenResp.Name)
Expand All @@ -106,7 +124,7 @@ func Test_AccessTokens(t *testing.T) {
})

t.Run("Delete Access Token", func(t *testing.T) {
if tokenResp.Id == nil {
if tokenResp == nil || tokenResp.Id == nil {
t.Skip("Skipping delete test because token creation failed")
}
err := client.DeleteAccessToken(ctx, &user, tokenResp.Id)
Expand All @@ -118,7 +136,7 @@ func Test_AccessTokens(t *testing.T) {

t.Run("Create Access Token With invalid user", func(t *testing.T) {
invalidUser := "roothyd"
tokenName := fmt.Sprintf("Token-%d-%d", time.Now().UnixNano(), rand.Int())
tokenName := fmt.Sprintf("Token-%s", uuid.New().String())
t.Logf("Create Access Token With invalid user - Creating token with name: %s\n", tokenName)
req := arangodb.AccessTokenRequest{
Name: utils.NewType(tokenName),
Expand All @@ -137,7 +155,7 @@ func Test_AccessTokens(t *testing.T) {
})

t.Run("Create Access Token With missing user", func(t *testing.T) {
tokenName := fmt.Sprintf("Token-%d-%d", time.Now().UnixNano(), rand.Int())
tokenName := fmt.Sprintf("Token-%s", uuid.New().String())
t.Logf("Create Access Token With missing user - Creating token with name: %s\n", tokenName)
localExpiresAt := time.Now().Add(5 * time.Minute).Unix()
req := arangodb.AccessTokenRequest{
Expand Down Expand Up @@ -174,3 +192,23 @@ func Test_AccessTokens(t *testing.T) {
})
})
}

// Cleanup tokens with the same name
func cleanupToken(ctx context.Context, t *testing.T, client arangodb.Client, user string, tokenName string) {
tokens, err := client.GetAllAccessToken(ctx, &user)
if err != nil {
t.Logf("Failed to list tokens for cleanup: %v", err)
return
}

for _, token := range tokens.Tokens {
if token.Name != nil && *token.Name == tokenName {
if token.Id != nil {
err := client.DeleteAccessToken(ctx, &user, token.Id)
if err != nil {
t.Logf("Failed to delete token %s: %v", *token.Name, err)
}
}
}
}
}