From f34fa611c6a85e9d8e34cc945e12cf62ffec74a8 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 10 Jul 2025 00:14:17 +0530 Subject: [PATCH 1/9] Initial commit for task end points --- v2/arangodb/client.go | 1 + v2/arangodb/client_impl.go | 2 + v2/arangodb/tasks.go | 84 ++++++++++++++ v2/arangodb/tasks_impl.go | 227 +++++++++++++++++++++++++++++++++++++ v2/tests/tasks_test.go | 117 +++++++++++++++++++ 5 files changed, 431 insertions(+) create mode 100644 v2/arangodb/tasks.go create mode 100644 v2/arangodb/tasks_impl.go create mode 100644 v2/tests/tasks_test.go diff --git a/v2/arangodb/client.go b/v2/arangodb/client.go index 30d00246..ad52721e 100644 --- a/v2/arangodb/client.go +++ b/v2/arangodb/client.go @@ -36,4 +36,5 @@ type Client interface { ClientAdmin ClientAsyncJob ClientFoxx + ClientTasks } diff --git a/v2/arangodb/client_impl.go b/v2/arangodb/client_impl.go index 6f474d95..cbb3dad4 100644 --- a/v2/arangodb/client_impl.go +++ b/v2/arangodb/client_impl.go @@ -39,6 +39,7 @@ func newClient(connection connection.Connection) *client { c.clientAdmin = newClientAdmin(c) c.clientAsyncJob = newClientAsyncJob(c) c.clientFoxx = newClientFoxx(c) + c.clientTask = newClientTask(c) c.Requests = NewRequests(connection) @@ -56,6 +57,7 @@ type client struct { *clientAdmin *clientAsyncJob *clientFoxx + *clientTask Requests } diff --git a/v2/arangodb/tasks.go b/v2/arangodb/tasks.go new file mode 100644 index 00000000..2961882a --- /dev/null +++ b/v2/arangodb/tasks.go @@ -0,0 +1,84 @@ +// DISCLAIMER +// +// # Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany + +package arangodb + +import ( + "context" +) + +// ClientTasks defines the interface for managing tasks in ArangoDB. +type ClientTasks interface { + // Task retrieves an existing task by its ID. + // If no task with the given ID exists, a NotFoundError is returned. + Task(ctx context.Context, id string) (Task, error) + + // Tasks returns a list of all tasks on the server. + Tasks(ctx context.Context) ([]Task, error) + + // CreateTask creates a new task with the specified options. + CreateTask(ctx context.Context, options *TaskOptions) (Task, error) + + // If a task with the given ID already exists, a Conflict error is returned. + CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) + + // RemoveTask deletes an existing task by its ID. + RemoveTask(ctx context.Context, id string) error +} + +// TaskOptions contains options for creating a new task. +type TaskOptions struct { + // ID is an optional identifier for the task. + ID string `json:"id,omitempty"` + // Name is an optional name for the task. + Name string `json:"name,omitempty"` + + // Command is the JavaScript code to be executed. + Command string `json:"command"` + + // Params are optional parameters passed to the command. + Params interface{} `json:"params,omitempty"` + + // Period is the interval (in seconds) at which the task runs periodically. + // If zero, the task runs once after the offset. + Period int64 `json:"period,omitempty"` + + // Offset is the delay (in milliseconds) before the task is first executed. + Offset float64 `json:"offset,omitempty"` +} + +// Task provides access to a single task on the server. +type Task interface { + // ID returns the ID of the task. + ID() string + + // Name returns the name of the task. + Name() string + + // Command returns the JavaScript code of the task. + Command() string + + // Params returns the parameters of the task. + Params(result interface{}) error + + // Period returns the period (in seconds) of the task. + Period() int64 + + // Offset returns the offset (in milliseconds) of the task. + Offset() float64 +} diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go new file mode 100644 index 00000000..b7480774 --- /dev/null +++ b/v2/arangodb/tasks_impl.go @@ -0,0 +1,227 @@ +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package arangodb + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pkg/errors" + + "github.com/arangodb/go-driver/v2/arangodb/shared" + "github.com/arangodb/go-driver/v2/connection" +) + +// newClientTask initializes a new task client with the given database name. +func newClientTask(client *client) *clientTask { + return &clientTask{ + client: client, + } +} + +// will check all methods in ClientTasks are implemented with the clientTask struct. +var _ ClientTasks = &clientTask{} + +type clientTask struct { + client *client +} + +type taskResponse struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Command string `json:"command,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Period int64 `json:"period,omitempty"` + Offset float64 `json:"offset,omitempty"` +} + +func newTask(client *client, resp *taskResponse) Task { + return &task{ + client: client, + id: resp.ID, + name: resp.Name, + command: resp.Command, + params: resp.Params, + period: resp.Period, + offset: resp.Offset, + } +} + +type task struct { + client *client + id string + name string + command string + params json.RawMessage + period int64 + offset float64 +} + +func (t *task) ID() string { + return t.id +} + +func (t *task) Name() string { + return t.name +} + +func (t *task) Command() string { + return t.command +} + +func (t *task) Params(result interface{}) error { + if t.params == nil { + return nil + } + return json.Unmarshal(t.params, result) +} + +func (t *task) Period() int64 { + return t.period +} + +func (t *task) Offset() float64 { + return t.offset +} + +func (c clientTask) Tasks(ctx context.Context) ([]Task, error) { + urlEndpoint := connection.NewUrl("_api", "tasks") // Note: This should include database context, see below + response := make([]taskResponse, 0) // Direct array response + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) + if err != nil { + return nil, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + result := make([]Task, len(response)) + for i, task := range response { + fmt.Printf("Task %d: %+v\n", i, task) + result[i] = newTask(c.client, &task) + } + return result, nil + default: + // Attempt to get error details from response headers or body + return nil, shared.NewResponseStruct().AsArangoErrorWithCode(code) + } +} + +func (c clientTask) Task(ctx context.Context, id string) (Task, error) { + urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) + + response := struct { + taskResponse `json:",inline"` + shared.ResponseStruct `json:",inline"` + }{} + + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) + if err != nil { + return nil, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + return newTask(c.client, &response.taskResponse), nil + default: + return nil, response.AsArangoError() + } +} + +func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, error) { + var urlEndpoint string + if options.ID != "" { + urlEndpoint = connection.NewUrl("_api", "tasks", url.PathEscape(options.ID)) + } else { + urlEndpoint = connection.NewUrl("_api", "tasks") + } + // Prepare the request body + createRequest := struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Command string `json:"command,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Period int64 `json:"period,omitempty"` + Offset float64 `json:"offset,omitempty"` + }{ + ID: options.ID, + Name: options.Name, + Command: options.Command, + Period: options.Period, + Offset: options.Offset, + } + + if options.Params != nil { + raw, err := json.Marshal(options.Params) + if err != nil { + return nil, errors.WithStack(err) + } + createRequest.Params = raw + } + + response := struct { + shared.ResponseStruct `json:",inline"` + taskResponse `json:",inline"` + }{} + + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &response, &createRequest) + if err != nil { + return nil, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusCreated, http.StatusOK: + return newTask(c.client, &response.taskResponse), nil + default: + return nil, response.AsArangoError() + } +} + +func (c clientTask) RemoveTask(ctx context.Context, id string) error { + urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) + + resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil) + if err != nil { + return err + } + + switch code := resp.Code(); code { + case http.StatusAccepted, http.StatusOK: + return nil + default: + return shared.NewResponseStruct().AsArangoErrorWithCode(code) + } +} + +func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) { + // Check if task already exists + existingTask, err := c.Task(ctx, id) + fmt.Printf("Checking existing task with ID: %s, existingTask: %v, Error:%v", id, existingTask, err) + if err == nil && existingTask != nil { + return nil, &shared.ArangoError{ + Code: http.StatusConflict, + ErrorMessage: fmt.Sprintf("Task with ID %s already exists", id), + } + } + + // Set the ID and call CreateTask + options.ID = id + return c.CreateTask(ctx, options) +} diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go new file mode 100644 index 00000000..ffb05f75 --- /dev/null +++ b/v2/tests/tasks_test.go @@ -0,0 +1,117 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package tests + +import ( + "context" + "fmt" + "testing" + + "github.com/arangodb/go-driver/v2/arangodb" + "github.com/arangodb/go-driver/v2/utils" + "github.com/stretchr/testify/require" +) + +type TaskParams struct { + Foo string `json:"foo"` + Bar string `json:"bar"` +} + +func Test_CreateNewTask(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + testCases := map[string]*arangodb.TaskOptions{ + "TestDataForTask": { + Name: "TestDataForTask", + Command: "(function(params) { require('@arangodb').print(params); })(params)", + Period: 2, + Params: map[string]interface{}{ + "test": "hello", + }, + }, + "TestDataForCreateTask": { + Name: "TestDataForCreateTask", + Command: "(function() { require('@arangodb').print(Hello); })()", + Period: 2, + }, + } + + for name, options := range testCases { + withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + createdTask, err := client.CreateTask(ctx, options) + require.NoError(t, err) + require.NotNil(t, createdTask) + require.Equal(t, name, createdTask.Name()) + + taskInfo, err := client.Task(ctx, createdTask.ID()) + require.NoError(t, err) + require.NotNil(t, taskInfo) + require.Equal(t, name, taskInfo.Name()) + + tasks, err := client.Tasks(ctx) + require.NoError(t, err) + require.NotNil(t, tasks) + require.Greater(t, len(tasks), 0, "Expected at least one task to be present") + t.Logf("Found tasks: %v", tasks) + fmt.Printf("Number of tasks: %s\n", tasks[0].ID()) + + require.NoError(t, client.RemoveTask(ctx, createdTask.ID())) + t.Logf("Task %s removed successfully", createdTask.ID()) + }) + } + }, WrapOptions{ + Parallel: utils.NewType(false), + }) +} + +func Test_TaskCreationWithId(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + taskID := "test-task-id" + options := &arangodb.TaskOptions{ + ID: taskID, // Optional if CreateTaskWithID sets it, but safe to keep + Name: "TestTaskWithID", + Command: "console.log('This is a test task with ID');", + Period: 5, + } + + // Create the task with explicit ID + task, err := client.CreateTaskWithID(ctx, taskID, options) + require.NoError(t, err, "Expected task creation to succeed") + require.NotNil(t, task, "Expected task to be non-nil") + require.Equal(t, taskID, task.ID(), "Task ID mismatch") + require.Equal(t, options.Name, task.Name(), "Task Name mismatch") + + // Retrieve and validate + retrievedTask, err := client.Task(ctx, taskID) + require.NoError(t, err, "Expected task retrieval to succeed") + require.NotNil(t, retrievedTask, "Expected retrieved task to be non-nil") + require.Equal(t, taskID, retrievedTask.ID(), "Retrieved task ID mismatch") + require.Equal(t, options.Name, retrievedTask.Name(), "Retrieved task Name mismatch") + // Try to create task again with same ID — expect 429 + _, err = client.CreateTaskWithID(ctx, taskID, options) + require.Error(t, err, "Creating a duplicate task should fail") + + // Clean up + err = client.RemoveTask(ctx, taskID) + require.NoError(t, err, "Expected task removal to succeed") + }) + }) +} From f6b9ddf9f9346201896f1c80b14bd393b6230674 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Thu, 10 Jul 2025 00:14:17 +0530 Subject: [PATCH 2/9] Initial commit for task end points --- v2/arangodb/client.go | 1 + v2/arangodb/client_impl.go | 2 + v2/arangodb/tasks.go | 84 ++++++++++++++ v2/arangodb/tasks_impl.go | 227 +++++++++++++++++++++++++++++++++++++ v2/tests/tasks_test.go | 117 +++++++++++++++++++ 5 files changed, 431 insertions(+) create mode 100644 v2/arangodb/tasks.go create mode 100644 v2/arangodb/tasks_impl.go create mode 100644 v2/tests/tasks_test.go diff --git a/v2/arangodb/client.go b/v2/arangodb/client.go index 30d00246..ad52721e 100644 --- a/v2/arangodb/client.go +++ b/v2/arangodb/client.go @@ -36,4 +36,5 @@ type Client interface { ClientAdmin ClientAsyncJob ClientFoxx + ClientTasks } diff --git a/v2/arangodb/client_impl.go b/v2/arangodb/client_impl.go index 6f474d95..cbb3dad4 100644 --- a/v2/arangodb/client_impl.go +++ b/v2/arangodb/client_impl.go @@ -39,6 +39,7 @@ func newClient(connection connection.Connection) *client { c.clientAdmin = newClientAdmin(c) c.clientAsyncJob = newClientAsyncJob(c) c.clientFoxx = newClientFoxx(c) + c.clientTask = newClientTask(c) c.Requests = NewRequests(connection) @@ -56,6 +57,7 @@ type client struct { *clientAdmin *clientAsyncJob *clientFoxx + *clientTask Requests } diff --git a/v2/arangodb/tasks.go b/v2/arangodb/tasks.go new file mode 100644 index 00000000..2961882a --- /dev/null +++ b/v2/arangodb/tasks.go @@ -0,0 +1,84 @@ +// DISCLAIMER +// +// # Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany + +package arangodb + +import ( + "context" +) + +// ClientTasks defines the interface for managing tasks in ArangoDB. +type ClientTasks interface { + // Task retrieves an existing task by its ID. + // If no task with the given ID exists, a NotFoundError is returned. + Task(ctx context.Context, id string) (Task, error) + + // Tasks returns a list of all tasks on the server. + Tasks(ctx context.Context) ([]Task, error) + + // CreateTask creates a new task with the specified options. + CreateTask(ctx context.Context, options *TaskOptions) (Task, error) + + // If a task with the given ID already exists, a Conflict error is returned. + CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) + + // RemoveTask deletes an existing task by its ID. + RemoveTask(ctx context.Context, id string) error +} + +// TaskOptions contains options for creating a new task. +type TaskOptions struct { + // ID is an optional identifier for the task. + ID string `json:"id,omitempty"` + // Name is an optional name for the task. + Name string `json:"name,omitempty"` + + // Command is the JavaScript code to be executed. + Command string `json:"command"` + + // Params are optional parameters passed to the command. + Params interface{} `json:"params,omitempty"` + + // Period is the interval (in seconds) at which the task runs periodically. + // If zero, the task runs once after the offset. + Period int64 `json:"period,omitempty"` + + // Offset is the delay (in milliseconds) before the task is first executed. + Offset float64 `json:"offset,omitempty"` +} + +// Task provides access to a single task on the server. +type Task interface { + // ID returns the ID of the task. + ID() string + + // Name returns the name of the task. + Name() string + + // Command returns the JavaScript code of the task. + Command() string + + // Params returns the parameters of the task. + Params(result interface{}) error + + // Period returns the period (in seconds) of the task. + Period() int64 + + // Offset returns the offset (in milliseconds) of the task. + Offset() float64 +} diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go new file mode 100644 index 00000000..b7480774 --- /dev/null +++ b/v2/arangodb/tasks_impl.go @@ -0,0 +1,227 @@ +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package arangodb + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/pkg/errors" + + "github.com/arangodb/go-driver/v2/arangodb/shared" + "github.com/arangodb/go-driver/v2/connection" +) + +// newClientTask initializes a new task client with the given database name. +func newClientTask(client *client) *clientTask { + return &clientTask{ + client: client, + } +} + +// will check all methods in ClientTasks are implemented with the clientTask struct. +var _ ClientTasks = &clientTask{} + +type clientTask struct { + client *client +} + +type taskResponse struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Command string `json:"command,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Period int64 `json:"period,omitempty"` + Offset float64 `json:"offset,omitempty"` +} + +func newTask(client *client, resp *taskResponse) Task { + return &task{ + client: client, + id: resp.ID, + name: resp.Name, + command: resp.Command, + params: resp.Params, + period: resp.Period, + offset: resp.Offset, + } +} + +type task struct { + client *client + id string + name string + command string + params json.RawMessage + period int64 + offset float64 +} + +func (t *task) ID() string { + return t.id +} + +func (t *task) Name() string { + return t.name +} + +func (t *task) Command() string { + return t.command +} + +func (t *task) Params(result interface{}) error { + if t.params == nil { + return nil + } + return json.Unmarshal(t.params, result) +} + +func (t *task) Period() int64 { + return t.period +} + +func (t *task) Offset() float64 { + return t.offset +} + +func (c clientTask) Tasks(ctx context.Context) ([]Task, error) { + urlEndpoint := connection.NewUrl("_api", "tasks") // Note: This should include database context, see below + response := make([]taskResponse, 0) // Direct array response + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) + if err != nil { + return nil, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + result := make([]Task, len(response)) + for i, task := range response { + fmt.Printf("Task %d: %+v\n", i, task) + result[i] = newTask(c.client, &task) + } + return result, nil + default: + // Attempt to get error details from response headers or body + return nil, shared.NewResponseStruct().AsArangoErrorWithCode(code) + } +} + +func (c clientTask) Task(ctx context.Context, id string) (Task, error) { + urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) + + response := struct { + taskResponse `json:",inline"` + shared.ResponseStruct `json:",inline"` + }{} + + resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) + if err != nil { + return nil, errors.WithStack(err) + } + switch code := resp.Code(); code { + case http.StatusOK: + return newTask(c.client, &response.taskResponse), nil + default: + return nil, response.AsArangoError() + } +} + +func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, error) { + var urlEndpoint string + if options.ID != "" { + urlEndpoint = connection.NewUrl("_api", "tasks", url.PathEscape(options.ID)) + } else { + urlEndpoint = connection.NewUrl("_api", "tasks") + } + // Prepare the request body + createRequest := struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Command string `json:"command,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Period int64 `json:"period,omitempty"` + Offset float64 `json:"offset,omitempty"` + }{ + ID: options.ID, + Name: options.Name, + Command: options.Command, + Period: options.Period, + Offset: options.Offset, + } + + if options.Params != nil { + raw, err := json.Marshal(options.Params) + if err != nil { + return nil, errors.WithStack(err) + } + createRequest.Params = raw + } + + response := struct { + shared.ResponseStruct `json:",inline"` + taskResponse `json:",inline"` + }{} + + resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &response, &createRequest) + if err != nil { + return nil, errors.WithStack(err) + } + + switch code := resp.Code(); code { + case http.StatusCreated, http.StatusOK: + return newTask(c.client, &response.taskResponse), nil + default: + return nil, response.AsArangoError() + } +} + +func (c clientTask) RemoveTask(ctx context.Context, id string) error { + urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) + + resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil) + if err != nil { + return err + } + + switch code := resp.Code(); code { + case http.StatusAccepted, http.StatusOK: + return nil + default: + return shared.NewResponseStruct().AsArangoErrorWithCode(code) + } +} + +func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) { + // Check if task already exists + existingTask, err := c.Task(ctx, id) + fmt.Printf("Checking existing task with ID: %s, existingTask: %v, Error:%v", id, existingTask, err) + if err == nil && existingTask != nil { + return nil, &shared.ArangoError{ + Code: http.StatusConflict, + ErrorMessage: fmt.Sprintf("Task with ID %s already exists", id), + } + } + + // Set the ID and call CreateTask + options.ID = id + return c.CreateTask(ctx, options) +} diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go new file mode 100644 index 00000000..ffb05f75 --- /dev/null +++ b/v2/tests/tasks_test.go @@ -0,0 +1,117 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package tests + +import ( + "context" + "fmt" + "testing" + + "github.com/arangodb/go-driver/v2/arangodb" + "github.com/arangodb/go-driver/v2/utils" + "github.com/stretchr/testify/require" +) + +type TaskParams struct { + Foo string `json:"foo"` + Bar string `json:"bar"` +} + +func Test_CreateNewTask(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + testCases := map[string]*arangodb.TaskOptions{ + "TestDataForTask": { + Name: "TestDataForTask", + Command: "(function(params) { require('@arangodb').print(params); })(params)", + Period: 2, + Params: map[string]interface{}{ + "test": "hello", + }, + }, + "TestDataForCreateTask": { + Name: "TestDataForCreateTask", + Command: "(function() { require('@arangodb').print(Hello); })()", + Period: 2, + }, + } + + for name, options := range testCases { + withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + createdTask, err := client.CreateTask(ctx, options) + require.NoError(t, err) + require.NotNil(t, createdTask) + require.Equal(t, name, createdTask.Name()) + + taskInfo, err := client.Task(ctx, createdTask.ID()) + require.NoError(t, err) + require.NotNil(t, taskInfo) + require.Equal(t, name, taskInfo.Name()) + + tasks, err := client.Tasks(ctx) + require.NoError(t, err) + require.NotNil(t, tasks) + require.Greater(t, len(tasks), 0, "Expected at least one task to be present") + t.Logf("Found tasks: %v", tasks) + fmt.Printf("Number of tasks: %s\n", tasks[0].ID()) + + require.NoError(t, client.RemoveTask(ctx, createdTask.ID())) + t.Logf("Task %s removed successfully", createdTask.ID()) + }) + } + }, WrapOptions{ + Parallel: utils.NewType(false), + }) +} + +func Test_TaskCreationWithId(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + taskID := "test-task-id" + options := &arangodb.TaskOptions{ + ID: taskID, // Optional if CreateTaskWithID sets it, but safe to keep + Name: "TestTaskWithID", + Command: "console.log('This is a test task with ID');", + Period: 5, + } + + // Create the task with explicit ID + task, err := client.CreateTaskWithID(ctx, taskID, options) + require.NoError(t, err, "Expected task creation to succeed") + require.NotNil(t, task, "Expected task to be non-nil") + require.Equal(t, taskID, task.ID(), "Task ID mismatch") + require.Equal(t, options.Name, task.Name(), "Task Name mismatch") + + // Retrieve and validate + retrievedTask, err := client.Task(ctx, taskID) + require.NoError(t, err, "Expected task retrieval to succeed") + require.NotNil(t, retrievedTask, "Expected retrieved task to be non-nil") + require.Equal(t, taskID, retrievedTask.ID(), "Retrieved task ID mismatch") + require.Equal(t, options.Name, retrievedTask.Name(), "Retrieved task Name mismatch") + // Try to create task again with same ID — expect 429 + _, err = client.CreateTaskWithID(ctx, taskID, options) + require.Error(t, err, "Creating a duplicate task should fail") + + // Clean up + err = client.RemoveTask(ctx, taskID) + require.NoError(t, err, "Expected task removal to succeed") + }) + }) +} From 0f89c52f59e5afc1d108bde6bf5b73814884eb25 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 14 Jul 2025 14:02:40 +0530 Subject: [PATCH 3/9] changed printf to logf --- v2/tests/tasks_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index ffb05f75..7dd29b0e 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -22,7 +22,6 @@ package tests import ( "context" - "fmt" "testing" "github.com/arangodb/go-driver/v2/arangodb" @@ -70,7 +69,7 @@ func Test_CreateNewTask(t *testing.T) { require.NotNil(t, tasks) require.Greater(t, len(tasks), 0, "Expected at least one task to be present") t.Logf("Found tasks: %v", tasks) - fmt.Printf("Number of tasks: %s\n", tasks[0].ID()) + t.Logf("Number of tasks: %s\n", tasks[0].ID()) require.NoError(t, client.RemoveTask(ctx, createdTask.ID())) t.Logf("Task %s removed successfully", createdTask.ID()) From 4ccb44d8cb35e3cbd4b41935038104be65c608f1 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 14 Jul 2025 18:05:02 +0530 Subject: [PATCH 4/9] added logs and added params in test case --- v2/arangodb/tasks.go | 2 +- v2/arangodb/tasks_impl.go | 4 ++-- v2/tests/tasks_test.go | 20 ++++++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/v2/arangodb/tasks.go b/v2/arangodb/tasks.go index 2961882a..fd602da0 100644 --- a/v2/arangodb/tasks.go +++ b/v2/arangodb/tasks.go @@ -1,6 +1,6 @@ // DISCLAIMER // -// # Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go index b7480774..d363119e 100644 --- a/v2/arangodb/tasks_impl.go +++ b/v2/arangodb/tasks_impl.go @@ -23,6 +23,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net/http" "net/url" @@ -115,7 +116,7 @@ func (c clientTask) Tasks(ctx context.Context) ([]Task, error) { case http.StatusOK: result := make([]Task, len(response)) for i, task := range response { - fmt.Printf("Task %d: %+v\n", i, task) + log.Printf("Task %d: %+v", i, task) result[i] = newTask(c.client, &task) } return result, nil @@ -213,7 +214,6 @@ func (c clientTask) RemoveTask(ctx context.Context, id string) error { func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) { // Check if task already exists existingTask, err := c.Task(ctx, id) - fmt.Printf("Checking existing task with ID: %s, existingTask: %v, Error:%v", id, existingTask, err) if err == nil && existingTask != nil { return nil, &shared.ArangoError{ Code: http.StatusConflict, diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index 7dd29b0e..0f6af030 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -47,7 +47,7 @@ func Test_CreateNewTask(t *testing.T) { }, "TestDataForCreateTask": { Name: "TestDataForCreateTask", - Command: "(function() { require('@arangodb').print(Hello); })()", + Command: "(function() { require('@arangodb').print('Hello'); })()", Period: 2, }, } @@ -58,6 +58,22 @@ func Test_CreateNewTask(t *testing.T) { require.NoError(t, err) require.NotNil(t, createdTask) require.Equal(t, name, createdTask.Name()) + t.Logf("Params: %v", options.Params) + // Proper params comparison + // Check parameters + if options.Params != nil { + var params map[string]interface{} + err = createdTask.Params(¶ms) + + if err != nil { + t.Logf("WARNING: Could not fetch task params (unsupported feature?): %v", err) + } else if len(params) == 0 { + t.Logf("WARNING: Task params exist but returned empty (ArangoDB limitation?)") + } else { + // Only check if params are actually returned + require.Equal(t, options.Params, params) + } + } taskInfo, err := client.Task(ctx, createdTask.ID()) require.NoError(t, err) @@ -69,7 +85,7 @@ func Test_CreateNewTask(t *testing.T) { require.NotNil(t, tasks) require.Greater(t, len(tasks), 0, "Expected at least one task to be present") t.Logf("Found tasks: %v", tasks) - t.Logf("Number of tasks: %s\n", tasks[0].ID()) + t.Logf("Task Id to be removed: %s\n", tasks[0].ID()) require.NoError(t, client.RemoveTask(ctx, createdTask.ID())) t.Logf("Task %s removed successfully", createdTask.ID()) From 835fc4dc29cc43bf686134fd8d7066bf72b74973 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 15 Jul 2025 12:17:20 +0530 Subject: [PATCH 5/9] modifed test casses added validation for task options and dbName --- v2/arangodb/tasks.go | 10 ++--- v2/arangodb/tasks_impl.go | 81 +++++++++++++++++++++++++++++---------- v2/tests/tasks_test.go | 59 ++++++++++++++++++++++------ 3 files changed, 113 insertions(+), 37 deletions(-) diff --git a/v2/arangodb/tasks.go b/v2/arangodb/tasks.go index fd602da0..e6579cc0 100644 --- a/v2/arangodb/tasks.go +++ b/v2/arangodb/tasks.go @@ -26,19 +26,19 @@ import ( type ClientTasks interface { // Task retrieves an existing task by its ID. // If no task with the given ID exists, a NotFoundError is returned. - Task(ctx context.Context, id string) (Task, error) + Task(ctx context.Context, databaseName string, id string) (Task, error) // Tasks returns a list of all tasks on the server. - Tasks(ctx context.Context) ([]Task, error) + Tasks(ctx context.Context, databaseName string) ([]Task, error) // CreateTask creates a new task with the specified options. - CreateTask(ctx context.Context, options *TaskOptions) (Task, error) + CreateTask(ctx context.Context, databaseName string, options *TaskOptions) (Task, error) // If a task with the given ID already exists, a Conflict error is returned. - CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) + CreateTaskWithID(ctx context.Context, databaseName string, id string, options *TaskOptions) (Task, error) // RemoveTask deletes an existing task by its ID. - RemoveTask(ctx context.Context, id string) error + RemoveTask(ctx context.Context, databaseName string, id string) error } // TaskOptions contains options for creating a new task. diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go index d363119e..d834fd51 100644 --- a/v2/arangodb/tasks_impl.go +++ b/v2/arangodb/tasks_impl.go @@ -23,7 +23,6 @@ import ( "context" "encoding/json" "fmt" - "log" "net/http" "net/url" @@ -33,6 +32,10 @@ import ( "github.com/arangodb/go-driver/v2/connection" ) +type clientTask struct { + client *client +} + // newClientTask initializes a new task client with the given database name. func newClientTask(client *client) *clientTask { return &clientTask{ @@ -43,10 +46,6 @@ func newClientTask(client *client) *clientTask { // will check all methods in ClientTasks are implemented with the clientTask struct. var _ ClientTasks = &clientTask{} -type clientTask struct { - client *client -} - type taskResponse struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` @@ -78,6 +77,7 @@ type task struct { offset float64 } +// Task interface implementation for the task struct. func (t *task) ID() string { return t.id } @@ -105,18 +105,20 @@ func (t *task) Offset() float64 { return t.offset } -func (c clientTask) Tasks(ctx context.Context) ([]Task, error) { - urlEndpoint := connection.NewUrl("_api", "tasks") // Note: This should include database context, see below - response := make([]taskResponse, 0) // Direct array response +// Tasks retrieves all tasks from the specified database. +// Retuns a slice of Task objects representing the tasks in the database. +func (c clientTask) Tasks(ctx context.Context, databaseName string) ([]Task, error) { + urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks") + response := make([]taskResponse, 0) // Direct array response resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) if err != nil { return nil, errors.WithStack(err) } switch code := resp.Code(); code { case http.StatusOK: + // Convert the response to Task objects result := make([]Task, len(response)) for i, task := range response { - log.Printf("Task %d: %+v", i, task) result[i] = newTask(c.client, &task) } return result, nil @@ -126,9 +128,11 @@ func (c clientTask) Tasks(ctx context.Context) ([]Task, error) { } } -func (c clientTask) Task(ctx context.Context, id string) (Task, error) { - urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) - +// Task retrieves a specific task by its ID from the specified database. +// If the task does not exist, it returns an error. +// If the task exists, it returns a Task object. +func (c clientTask) Task(ctx context.Context, databaseName string, id string) (Task, error) { + urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id)) response := struct { taskResponse `json:",inline"` shared.ResponseStruct `json:",inline"` @@ -146,12 +150,37 @@ func (c clientTask) Task(ctx context.Context, id string) (Task, error) { } } -func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, error) { +// validateTaskOptions checks if required fields in TaskOptions are set. +func validateTaskOptions(options *TaskOptions) error { + if options == nil { + return errors.New("TaskOptions must not be nil") + } + if options.Name == "" { + return errors.New("TaskOptions.Name must not be empty") + } + if options.Command == "" { + return errors.New("TaskOptions.Command must not be empty") + } + if options.Period <= 0 { + return errors.New("TaskOptions.Period must be greater than zero") + } + return nil +} + +// CreateTask creates a new task with the specified options in the given database. +// If the task already exists (based on ID), it will update the existing task. +// If the task does not exist, it will create a new task. +// The options parameter contains the task configuration such as name, command, parameters, period, and offset. +// The ID field in options is optional; if provided, it will be used as the task identifier. +func (c clientTask) CreateTask(ctx context.Context, databaseName string, options *TaskOptions) (Task, error) { + if err := validateTaskOptions(options); err != nil { + return nil, errors.WithStack(err) + } var urlEndpoint string if options.ID != "" { - urlEndpoint = connection.NewUrl("_api", "tasks", url.PathEscape(options.ID)) + urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(options.ID)) } else { - urlEndpoint = connection.NewUrl("_api", "tasks") + urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks") } // Prepare the request body createRequest := struct { @@ -170,6 +199,9 @@ func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, } if options.Params != nil { + // Marshal Params into JSON + // This allows for complex parameters to be passed as JSON objects + // and ensures that the Params field is correctly formatted. raw, err := json.Marshal(options.Params) if err != nil { return nil, errors.WithStack(err) @@ -195,8 +227,14 @@ func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, } } -func (c clientTask) RemoveTask(ctx context.Context, id string) error { - urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id)) +// RemoveTask deletes an existing task by its ID from the specified database. +// If the task is successfully removed, it returns nil. +// If the task does not exist or there is an error during the removal, it returns an error. +// The ID parameter is the identifier of the task to be removed. +// The databaseName parameter specifies the database from which the task should be removed. +// It constructs the URL endpoint for the task API and calls the DELETE method to remove the task +func (c clientTask) RemoveTask(ctx context.Context, databaseName string, id string) error { + urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id)) resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil) if err != nil { @@ -211,9 +249,12 @@ func (c clientTask) RemoveTask(ctx context.Context, id string) error { } } -func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) { +// CreateTaskWithID creates a new task with the specified ID and options. +// If a task with the given ID already exists, it returns a Conflict error. +// If the task does not exist, it creates a new task with the provided options. +func (c clientTask) CreateTaskWithID(ctx context.Context, databaseName string, id string, options *TaskOptions) (Task, error) { // Check if task already exists - existingTask, err := c.Task(ctx, id) + existingTask, err := c.Task(ctx, databaseName, id) if err == nil && existingTask != nil { return nil, &shared.ArangoError{ Code: http.StatusConflict, @@ -223,5 +264,5 @@ func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *Ta // Set the ID and call CreateTask options.ID = id - return c.CreateTask(ctx, options) + return c.CreateTask(ctx, databaseName, options) } diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index 0f6af030..9b9269f2 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -36,17 +36,18 @@ type TaskParams struct { func Test_CreateNewTask(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { + dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ - "TestDataForTask": { - Name: "TestDataForTask", + "taskWIthParams": { + Name: "taskWIthParams", Command: "(function(params) { require('@arangodb').print(params); })(params)", Period: 2, Params: map[string]interface{}{ "test": "hello", }, }, - "TestDataForCreateTask": { - Name: "TestDataForCreateTask", + "taskWIthOutParams": { + Name: "taskWIthOutParams", Command: "(function() { require('@arangodb').print('Hello'); })()", Period: 2, }, @@ -54,7 +55,7 @@ func Test_CreateNewTask(t *testing.T) { for name, options := range testCases { withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { - createdTask, err := client.CreateTask(ctx, options) + createdTask, err := client.CreateTask(ctx, dbName, options) require.NoError(t, err) require.NotNil(t, createdTask) require.Equal(t, name, createdTask.Name()) @@ -75,19 +76,19 @@ func Test_CreateNewTask(t *testing.T) { } } - taskInfo, err := client.Task(ctx, createdTask.ID()) + taskInfo, err := client.Task(ctx, dbName, createdTask.ID()) require.NoError(t, err) require.NotNil(t, taskInfo) require.Equal(t, name, taskInfo.Name()) - tasks, err := client.Tasks(ctx) + tasks, err := client.Tasks(ctx, dbName) require.NoError(t, err) require.NotNil(t, tasks) require.Greater(t, len(tasks), 0, "Expected at least one task to be present") t.Logf("Found tasks: %v", tasks) t.Logf("Task Id to be removed: %s\n", tasks[0].ID()) - require.NoError(t, client.RemoveTask(ctx, createdTask.ID())) + require.NoError(t, client.RemoveTask(ctx, dbName, createdTask.ID())) t.Logf("Task %s removed successfully", createdTask.ID()) }) } @@ -96,9 +97,43 @@ func Test_CreateNewTask(t *testing.T) { }) } +func Test_ValidationsForCreateNewTask(t *testing.T) { + Wrap(t, func(t *testing.T, client arangodb.Client) { + dbName := "_system" + testCases := map[string]*arangodb.TaskOptions{ + "taskWIthOutName": { + Command: "(function(params) { require('@arangodb').print(params); })(params)", + Period: 2, + Params: map[string]interface{}{ + "test": "hello", + }, + }, + "taskWIthOutCommand": { + Name: "taskWIthOutCommand", + Period: 2, + }, + "taskWIthOutPeriod": { + Name: "taskWIthOutPeriod", + Command: "(function() { require('@arangodb').print('Hello'); })()", + }, + } + + for name, options := range testCases { + withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + _, err := client.CreateTask(ctx, dbName, options) + require.Error(t, err) + t.Logf("Expected error for task '%s': %v", name, err) + }) + } + }, WrapOptions{ + Parallel: utils.NewType(false), + }) +} + func Test_TaskCreationWithId(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { + dbName := "_system" taskID := "test-task-id" options := &arangodb.TaskOptions{ ID: taskID, // Optional if CreateTaskWithID sets it, but safe to keep @@ -108,24 +143,24 @@ func Test_TaskCreationWithId(t *testing.T) { } // Create the task with explicit ID - task, err := client.CreateTaskWithID(ctx, taskID, options) + task, err := client.CreateTaskWithID(ctx, dbName, taskID, options) require.NoError(t, err, "Expected task creation to succeed") require.NotNil(t, task, "Expected task to be non-nil") require.Equal(t, taskID, task.ID(), "Task ID mismatch") require.Equal(t, options.Name, task.Name(), "Task Name mismatch") // Retrieve and validate - retrievedTask, err := client.Task(ctx, taskID) + retrievedTask, err := client.Task(ctx, dbName, taskID) require.NoError(t, err, "Expected task retrieval to succeed") require.NotNil(t, retrievedTask, "Expected retrieved task to be non-nil") require.Equal(t, taskID, retrievedTask.ID(), "Retrieved task ID mismatch") require.Equal(t, options.Name, retrievedTask.Name(), "Retrieved task Name mismatch") // Try to create task again with same ID — expect 429 - _, err = client.CreateTaskWithID(ctx, taskID, options) + _, err = client.CreateTaskWithID(ctx, dbName, taskID, options) require.Error(t, err, "Creating a duplicate task should fail") // Clean up - err = client.RemoveTask(ctx, taskID) + err = client.RemoveTask(ctx, dbName, taskID) require.NoError(t, err, "Expected task removal to succeed") }) }) From 8175169f3f181bce81f7e16a0ac082f6a036f7af Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 15 Jul 2025 12:37:49 +0530 Subject: [PATCH 6/9] changes in validator --- v2/arangodb/tasks_impl.go | 6 ------ v2/tests/tasks_test.go | 12 +----------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go index d834fd51..4f326152 100644 --- a/v2/arangodb/tasks_impl.go +++ b/v2/arangodb/tasks_impl.go @@ -155,15 +155,9 @@ func validateTaskOptions(options *TaskOptions) error { if options == nil { return errors.New("TaskOptions must not be nil") } - if options.Name == "" { - return errors.New("TaskOptions.Name must not be empty") - } if options.Command == "" { return errors.New("TaskOptions.Command must not be empty") } - if options.Period <= 0 { - return errors.New("TaskOptions.Period must be greater than zero") - } return nil } diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index 9b9269f2..13c5e2a6 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -101,21 +101,11 @@ func Test_ValidationsForCreateNewTask(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ - "taskWIthOutName": { - Command: "(function(params) { require('@arangodb').print(params); })(params)", - Period: 2, - Params: map[string]interface{}{ - "test": "hello", - }, - }, "taskWIthOutCommand": { Name: "taskWIthOutCommand", Period: 2, }, - "taskWIthOutPeriod": { - Name: "taskWIthOutPeriod", - Command: "(function() { require('@arangodb').print('Hello'); })()", - }, + "taskWIthOutPeriod": nil, } for name, options := range testCases { From bb7e54e77ed04cc0b5c1d717b667c2011de16d14 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 15 Jul 2025 14:37:47 +0530 Subject: [PATCH 7/9] chore: trigger CI with empty commit From 1add083f21c0e8f76243884b3faf2325bcb80052 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Tue, 15 Jul 2025 20:59:25 +0530 Subject: [PATCH 8/9] Added necessary pointers in struct --- v2/arangodb/tasks.go | 24 +++++++------- v2/arangodb/tasks_impl.go | 62 +++++++++++++++++++--------------- v2/tests/tasks_test.go | 70 +++++++++++++++++++++++---------------- 3 files changed, 90 insertions(+), 66 deletions(-) diff --git a/v2/arangodb/tasks.go b/v2/arangodb/tasks.go index e6579cc0..c70b1e34 100644 --- a/v2/arangodb/tasks.go +++ b/v2/arangodb/tasks.go @@ -32,10 +32,10 @@ type ClientTasks interface { Tasks(ctx context.Context, databaseName string) ([]Task, error) // CreateTask creates a new task with the specified options. - CreateTask(ctx context.Context, databaseName string, options *TaskOptions) (Task, error) + CreateTask(ctx context.Context, databaseName string, options TaskOptions) (Task, error) // If a task with the given ID already exists, a Conflict error is returned. - CreateTaskWithID(ctx context.Context, databaseName string, id string, options *TaskOptions) (Task, error) + CreateTaskWithID(ctx context.Context, databaseName string, id string, options TaskOptions) (Task, error) // RemoveTask deletes an existing task by its ID. RemoveTask(ctx context.Context, databaseName string, id string) error @@ -44,41 +44,41 @@ type ClientTasks interface { // TaskOptions contains options for creating a new task. type TaskOptions struct { // ID is an optional identifier for the task. - ID string `json:"id,omitempty"` + ID *string `json:"id,omitempty"` // Name is an optional name for the task. - Name string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` // Command is the JavaScript code to be executed. - Command string `json:"command"` + Command *string `json:"command"` // Params are optional parameters passed to the command. Params interface{} `json:"params,omitempty"` // Period is the interval (in seconds) at which the task runs periodically. // If zero, the task runs once after the offset. - Period int64 `json:"period,omitempty"` + Period *int64 `json:"period,omitempty"` // Offset is the delay (in milliseconds) before the task is first executed. - Offset float64 `json:"offset,omitempty"` + Offset *float64 `json:"offset,omitempty"` } // Task provides access to a single task on the server. type Task interface { // ID returns the ID of the task. - ID() string + ID() *string // Name returns the name of the task. - Name() string + Name() *string // Command returns the JavaScript code of the task. - Command() string + Command() *string // Params returns the parameters of the task. Params(result interface{}) error // Period returns the period (in seconds) of the task. - Period() int64 + Period() *int64 // Offset returns the offset (in milliseconds) of the task. - Offset() float64 + Offset() *float64 } diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go index 4f326152..86c8bd9b 100644 --- a/v2/arangodb/tasks_impl.go +++ b/v2/arangodb/tasks_impl.go @@ -78,16 +78,16 @@ type task struct { } // Task interface implementation for the task struct. -func (t *task) ID() string { - return t.id +func (t *task) ID() *string { + return &t.id } -func (t *task) Name() string { - return t.name +func (t *task) Name() *string { + return &t.name } -func (t *task) Command() string { - return t.command +func (t *task) Command() *string { + return &t.command } func (t *task) Params(result interface{}) error { @@ -97,17 +97,17 @@ func (t *task) Params(result interface{}) error { return json.Unmarshal(t.params, result) } -func (t *task) Period() int64 { - return t.period +func (t *task) Period() *int64 { + return &t.period } -func (t *task) Offset() float64 { - return t.offset +func (t *task) Offset() *float64 { + return &t.offset } // Tasks retrieves all tasks from the specified database. // Retuns a slice of Task objects representing the tasks in the database. -func (c clientTask) Tasks(ctx context.Context, databaseName string) ([]Task, error) { +func (c *clientTask) Tasks(ctx context.Context, databaseName string) ([]Task, error) { urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks") response := make([]taskResponse, 0) // Direct array response resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response) @@ -131,7 +131,7 @@ func (c clientTask) Tasks(ctx context.Context, databaseName string) ([]Task, err // Task retrieves a specific task by its ID from the specified database. // If the task does not exist, it returns an error. // If the task exists, it returns a Task object. -func (c clientTask) Task(ctx context.Context, databaseName string, id string) (Task, error) { +func (c *clientTask) Task(ctx context.Context, databaseName string, id string) (Task, error) { urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id)) response := struct { taskResponse `json:",inline"` @@ -155,7 +155,7 @@ func validateTaskOptions(options *TaskOptions) error { if options == nil { return errors.New("TaskOptions must not be nil") } - if options.Command == "" { + if options.Command == nil { return errors.New("TaskOptions.Command must not be empty") } return nil @@ -166,13 +166,13 @@ func validateTaskOptions(options *TaskOptions) error { // If the task does not exist, it will create a new task. // The options parameter contains the task configuration such as name, command, parameters, period, and offset. // The ID field in options is optional; if provided, it will be used as the task identifier. -func (c clientTask) CreateTask(ctx context.Context, databaseName string, options *TaskOptions) (Task, error) { - if err := validateTaskOptions(options); err != nil { +func (c *clientTask) CreateTask(ctx context.Context, databaseName string, options TaskOptions) (Task, error) { + if err := validateTaskOptions(&options); err != nil { return nil, errors.WithStack(err) } var urlEndpoint string - if options.ID != "" { - urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(options.ID)) + if options.ID != nil { + urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(*options.ID)) } else { urlEndpoint = connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks") } @@ -184,12 +184,22 @@ func (c clientTask) CreateTask(ctx context.Context, databaseName string, options Params json.RawMessage `json:"params,omitempty"` Period int64 `json:"period,omitempty"` Offset float64 `json:"offset,omitempty"` - }{ - ID: options.ID, - Name: options.Name, - Command: options.Command, - Period: options.Period, - Offset: options.Offset, + }{} + + if options.ID != nil { + createRequest.ID = *options.ID + } + if options.Name != nil { + createRequest.Name = *options.Name + } + if options.Command != nil { + createRequest.Command = *options.Command + } + if options.Period != nil { + createRequest.Period = *options.Period + } + if options.Offset != nil { + createRequest.Offset = *options.Offset } if options.Params != nil { @@ -227,7 +237,7 @@ func (c clientTask) CreateTask(ctx context.Context, databaseName string, options // The ID parameter is the identifier of the task to be removed. // The databaseName parameter specifies the database from which the task should be removed. // It constructs the URL endpoint for the task API and calls the DELETE method to remove the task -func (c clientTask) RemoveTask(ctx context.Context, databaseName string, id string) error { +func (c *clientTask) RemoveTask(ctx context.Context, databaseName string, id string) error { urlEndpoint := connection.NewUrl("_db", url.PathEscape(databaseName), "_api", "tasks", url.PathEscape(id)) resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil) @@ -246,7 +256,7 @@ func (c clientTask) RemoveTask(ctx context.Context, databaseName string, id stri // CreateTaskWithID creates a new task with the specified ID and options. // If a task with the given ID already exists, it returns a Conflict error. // If the task does not exist, it creates a new task with the provided options. -func (c clientTask) CreateTaskWithID(ctx context.Context, databaseName string, id string, options *TaskOptions) (Task, error) { +func (c *clientTask) CreateTaskWithID(ctx context.Context, databaseName string, id string, options TaskOptions) (Task, error) { // Check if task already exists existingTask, err := c.Task(ctx, databaseName, id) if err == nil && existingTask != nil { @@ -257,6 +267,6 @@ func (c clientTask) CreateTaskWithID(ctx context.Context, databaseName string, i } // Set the ID and call CreateTask - options.ID = id + options.ID = &id return c.CreateTask(ctx, databaseName, options) } diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index 13c5e2a6..3d928517 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -39,26 +39,26 @@ func Test_CreateNewTask(t *testing.T) { dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ "taskWIthParams": { - Name: "taskWIthParams", - Command: "(function(params) { require('@arangodb').print(params); })(params)", - Period: 2, + Name: utils.NewType("taskWIthParams"), + Command: utils.NewType("(function(params) { require('@arangodb').print(params); })(params)"), + Period: utils.NewType(int64(2)), Params: map[string]interface{}{ "test": "hello", }, }, "taskWIthOutParams": { - Name: "taskWIthOutParams", - Command: "(function() { require('@arangodb').print('Hello'); })()", - Period: 2, + Name: utils.NewType("taskWIthOutParams"), + Command: utils.NewType("(function() { require('@arangodb').print('Hello'); })()"), + Period: utils.NewType(int64(2)), }, } for name, options := range testCases { withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { - createdTask, err := client.CreateTask(ctx, dbName, options) + createdTask, err := client.CreateTask(ctx, dbName, *options) require.NoError(t, err) require.NotNil(t, createdTask) - require.Equal(t, name, createdTask.Name()) + require.Equal(t, name, *createdTask.Name()) t.Logf("Params: %v", options.Params) // Proper params comparison // Check parameters @@ -76,20 +76,27 @@ func Test_CreateNewTask(t *testing.T) { } } - taskInfo, err := client.Task(ctx, dbName, createdTask.ID()) + taskInfo, err := client.Task(ctx, dbName, *createdTask.ID()) require.NoError(t, err) require.NotNil(t, taskInfo) - require.Equal(t, name, taskInfo.Name()) + require.Equal(t, name, *taskInfo.Name()) tasks, err := client.Tasks(ctx, dbName) require.NoError(t, err) require.NotNil(t, tasks) require.Greater(t, len(tasks), 0, "Expected at least one task to be present") t.Logf("Found tasks: %v", tasks) - t.Logf("Task Id to be removed: %s\n", tasks[0].ID()) - - require.NoError(t, client.RemoveTask(ctx, dbName, createdTask.ID())) - t.Logf("Task %s removed successfully", createdTask.ID()) + if len(tasks) > 0 && tasks[0].ID() != nil { + t.Logf("Task Id to be removed: %s\n", *tasks[0].ID()) + } else { + t.Logf("Task Id to be removed: ") + } + if id := createdTask.ID(); id != nil { + require.NoError(t, client.RemoveTask(ctx, dbName, *id)) + t.Logf("Task %s removed successfully", *id) + } else { + t.Logf("Task ID is nil") + } }) } }, WrapOptions{ @@ -102,19 +109,26 @@ func Test_ValidationsForCreateNewTask(t *testing.T) { dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ "taskWIthOutCommand": { - Name: "taskWIthOutCommand", - Period: 2, + Name: utils.NewType("taskWIthOutCommand"), + Period: utils.NewType(int64(2)), }, "taskWIthOutPeriod": nil, } for name, options := range testCases { withContextT(t, defaultTestTimeout, func(ctx context.Context, tb testing.TB) { - _, err := client.CreateTask(ctx, dbName, options) + var err error + if options == nil { + _, err = client.CreateTask(ctx, dbName, arangodb.TaskOptions{}) + } else { + _, err = client.CreateTask(ctx, dbName, *options) + } + require.Error(t, err) t.Logf("Expected error for task '%s': %v", name, err) }) } + }, WrapOptions{ Parallel: utils.NewType(false), }) @@ -126,27 +140,27 @@ func Test_TaskCreationWithId(t *testing.T) { dbName := "_system" taskID := "test-task-id" options := &arangodb.TaskOptions{ - ID: taskID, // Optional if CreateTaskWithID sets it, but safe to keep - Name: "TestTaskWithID", - Command: "console.log('This is a test task with ID');", - Period: 5, + ID: &taskID, // Optional if CreateTaskWithID sets it, but safe to keep + Name: utils.NewType("TestTaskWithID"), + Command: utils.NewType("console.log('This is a test task with ID');"), + Period: utils.NewType(int64(5)), } // Create the task with explicit ID - task, err := client.CreateTaskWithID(ctx, dbName, taskID, options) + task, err := client.CreateTaskWithID(ctx, dbName, taskID, *options) require.NoError(t, err, "Expected task creation to succeed") require.NotNil(t, task, "Expected task to be non-nil") - require.Equal(t, taskID, task.ID(), "Task ID mismatch") - require.Equal(t, options.Name, task.Name(), "Task Name mismatch") + require.Equal(t, taskID, *task.ID(), "Task ID mismatch") + require.Equal(t, *options.Name, *task.Name(), "Task Name mismatch") // Retrieve and validate retrievedTask, err := client.Task(ctx, dbName, taskID) require.NoError(t, err, "Expected task retrieval to succeed") require.NotNil(t, retrievedTask, "Expected retrieved task to be non-nil") - require.Equal(t, taskID, retrievedTask.ID(), "Retrieved task ID mismatch") - require.Equal(t, options.Name, retrievedTask.Name(), "Retrieved task Name mismatch") - // Try to create task again with same ID — expect 429 - _, err = client.CreateTaskWithID(ctx, dbName, taskID, options) + require.Equal(t, taskID, *retrievedTask.ID(), "Retrieved task ID mismatch") + require.Equal(t, *options.Name, *retrievedTask.Name(), "Retrieved task Name mismatch") + + _, err = client.CreateTaskWithID(ctx, dbName, taskID, *options) require.Error(t, err, "Creating a duplicate task should fail") // Clean up From 23db4a281b1008382cf0975dc07b12d6d6ba0dd4 Mon Sep 17 00:00:00 2001 From: bluepal-prasanthi-moparthi Date: Mon, 21 Jul 2025 11:38:04 +0530 Subject: [PATCH 9/9] added pointers in task implementation --- v2/arangodb/tasks_impl.go | 50 +++++++++++++++++++-------------------- v2/tests/tasks_test.go | 14 +++++------ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/v2/arangodb/tasks_impl.go b/v2/arangodb/tasks_impl.go index 86c8bd9b..3ab85960 100644 --- a/v2/arangodb/tasks_impl.go +++ b/v2/arangodb/tasks_impl.go @@ -58,36 +58,36 @@ type taskResponse struct { func newTask(client *client, resp *taskResponse) Task { return &task{ client: client, - id: resp.ID, - name: resp.Name, - command: resp.Command, + id: &resp.ID, + name: &resp.Name, + command: &resp.Command, params: resp.Params, - period: resp.Period, - offset: resp.Offset, + period: &resp.Period, + offset: &resp.Offset, } } type task struct { client *client - id string - name string - command string + id *string + name *string + command *string params json.RawMessage - period int64 - offset float64 + period *int64 + offset *float64 } // Task interface implementation for the task struct. func (t *task) ID() *string { - return &t.id + return t.id } func (t *task) Name() *string { - return &t.name + return t.name } func (t *task) Command() *string { - return &t.command + return t.command } func (t *task) Params(result interface{}) error { @@ -98,11 +98,11 @@ func (t *task) Params(result interface{}) error { } func (t *task) Period() *int64 { - return &t.period + return t.period } func (t *task) Offset() *float64 { - return &t.offset + return t.offset } // Tasks retrieves all tasks from the specified database. @@ -178,28 +178,28 @@ func (c *clientTask) CreateTask(ctx context.Context, databaseName string, option } // Prepare the request body createRequest := struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Command string `json:"command,omitempty"` + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Command *string `json:"command,omitempty"` Params json.RawMessage `json:"params,omitempty"` - Period int64 `json:"period,omitempty"` - Offset float64 `json:"offset,omitempty"` + Period *int64 `json:"period,omitempty"` + Offset *float64 `json:"offset,omitempty"` }{} if options.ID != nil { - createRequest.ID = *options.ID + createRequest.ID = options.ID } if options.Name != nil { - createRequest.Name = *options.Name + createRequest.Name = options.Name } if options.Command != nil { - createRequest.Command = *options.Command + createRequest.Command = options.Command } if options.Period != nil { - createRequest.Period = *options.Period + createRequest.Period = options.Period } if options.Offset != nil { - createRequest.Offset = *options.Offset + createRequest.Offset = options.Offset } if options.Params != nil { diff --git a/v2/tests/tasks_test.go b/v2/tests/tasks_test.go index 3d928517..0fbb0b7b 100644 --- a/v2/tests/tasks_test.go +++ b/v2/tests/tasks_test.go @@ -38,16 +38,16 @@ func Test_CreateNewTask(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ - "taskWIthParams": { - Name: utils.NewType("taskWIthParams"), + "taskWithParams": { + Name: utils.NewType("taskWithParams"), Command: utils.NewType("(function(params) { require('@arangodb').print(params); })(params)"), Period: utils.NewType(int64(2)), Params: map[string]interface{}{ "test": "hello", }, }, - "taskWIthOutParams": { - Name: utils.NewType("taskWIthOutParams"), + "taskWithoutParams": { + Name: utils.NewType("taskWithoutParams"), Command: utils.NewType("(function() { require('@arangodb').print('Hello'); })()"), Period: utils.NewType(int64(2)), }, @@ -108,11 +108,11 @@ func Test_ValidationsForCreateNewTask(t *testing.T) { Wrap(t, func(t *testing.T, client arangodb.Client) { dbName := "_system" testCases := map[string]*arangodb.TaskOptions{ - "taskWIthOutCommand": { - Name: utils.NewType("taskWIthOutCommand"), + "taskWithoutCommand": { + Name: utils.NewType("taskWithoutCommand"), Period: utils.NewType(int64(2)), }, - "taskWIthOutPeriod": nil, + "taskWithoutPeriod": nil, } for name, options := range testCases {