diff --git a/cmd/task/main.go b/cmd/task/main.go index d7853699eacdd6406531057c77fe0b3c8670c9c7..d65bbc215762363852ff9b0e50f28c81a478e1e6 100644 --- a/cmd/task/main.go +++ b/cmd/task/main.go @@ -107,7 +107,7 @@ func main() { ) { taskSvc = task.New(storage, storage, cache, logger) - taskListSvc = tasklist.New(storage, storage, logger) + taskListSvc = tasklist.New(storage, storage, cache, logger) healthSvc = health.New() } diff --git a/docs/task-list.md b/docs/task-list.md index 5b2b4fcf9e170d9923132004f31ebbb840d56376..ac9a564cf0c2532e4d02462ac4e3e4c6bf046b9f 100644 --- a/docs/task-list.md +++ b/docs/task-list.md @@ -55,9 +55,70 @@ an input to the next. If one task fails to execute, all following tasks are mark - Parallel group execution: tasks within the group are executed in parallel and the results are dependant. If a task fails to execute, this does not affect the other tasks but the group is marked with failed status. +### Task list State + The state of the task list asynchronous execution is available later on the `result` endpoint: ```shell curl -v -X GET http://localhost:8082/v1/taskListResult/{taskListID} +``` +The state is returned as an HTTP status code. +- Status code `200` for `Done` state; +- Status code `201` for `Created` state; +- Status code `202` for `Pending` state (the task list is being executed); +- Status code `207` for `Failed` state (at least one task within the task list has failed). + +Example responses: + +HTTP Response code `200`: Done +```json +{ + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done", + "groups": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "type": "sequential", + "status": "done", + "tasks": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done" + }, + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done" + } + ] + } + ] +} + +``` + +HTTP Response code `207`: Failed +```json +{ + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "failed", + "groups": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "type": "sequential", + "status": "failed", + "tasks": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done" + }, + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "failed" + } + ] + } + ] +} + ``` ### Task list Executor Configuration diff --git a/internal/listexecutor/listexecutor.go b/internal/listexecutor/listexecutor.go index b05bad414c12af2880cfce1296ea5c4473bbe1eb..ceea140563990f60fc7c47b2320ceac787877d93 100644 --- a/internal/listexecutor/listexecutor.go +++ b/internal/listexecutor/listexecutor.go @@ -3,6 +3,7 @@ package listexecutor import ( "bytes" "context" + "encoding/json" "io" "net/http" "sync" @@ -11,6 +12,8 @@ import ( "go.uber.org/zap" "code.vereign.com/gaiax/tsa/golib/errors" + "code.vereign.com/gaiax/tsa/golib/ptr" + goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" taskpkg "code.vereign.com/gaiax/tsa/task/internal/service/task" "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" ) @@ -125,13 +128,17 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) { list.State = taskpkg.Pending list.StartedAt = time.Now() + var state goatasklist.TaskListStatus + // execute groups sequentially for i := range list.Groups { - if err := l.executeGroup(ctx, &list.Groups[i]); err != nil { + groupState, err := l.executeGroup(ctx, &list.Groups[i]) + if err != nil { logger.Error("error executing group", zap.Error(err)) list.Groups[i].State = taskpkg.Failed list.State = taskpkg.Failed } + state.Groups = append(state.Groups, groupState) } if list.State != taskpkg.Failed { @@ -139,6 +146,20 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) { } list.FinishedAt = time.Now() + state.ID = list.ID + state.Status = string(list.State) + + value, err := json.Marshal(state) + if err != nil { + logger.Error("error marshaling taskList state", zap.Error(err)) + } else { + if err := l.cache.Set(ctx, list.ID, list.CacheNamespace, list.CacheScope, value); err != nil { + logger.Error("error storing taskList state in cache", zap.Error(err)) + } else { + logger.Debug("taskList state is stored in cache") + } + } + if err := l.storage.SaveTaskListHistory(ctx, list); err != nil { logger.Error("error saving taskList history", zap.Error(err)) } else { @@ -150,7 +171,7 @@ func (l *ListExecutor) Execute(ctx context.Context, list *tasklist.TaskList) { } } -func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group) error { +func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) { switch exec := group.Execution; exec { case sequential: return l.executeSequential(ctx, group) @@ -158,19 +179,22 @@ func (l *ListExecutor) executeGroup(ctx context.Context, group *tasklist.Group) return l.executeParallel(ctx, group) } - return errors.New("unknown type of group execution") + return nil, errors.New("unknown type of group execution") } -func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Group) error { +func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) { group.State = taskpkg.Pending + var state goatasklist.GroupStatus tasks, err := l.storage.GetGroupTasks(ctx, group) if err != nil { - return err + return nil, err } req := group.Request for _, task := range tasks { + taskState := goatasklist.TaskStatus{ID: &task.ID} + logger := l.logger.With( zap.String("taskID", task.ID), zap.String("taskName", task.Name), @@ -179,6 +203,8 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr // mark all subsequent tasks as failed if one task already failed if group.State == taskpkg.Failed { task.State = taskpkg.Failed + taskState.Status = ptr.String(taskpkg.Failed) + state.Tasks = append(state.Tasks, &taskState) continue } @@ -186,12 +212,17 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr err := l.executeTask(ctx, task) if err != nil { task.State = taskpkg.Failed + taskState.Status = ptr.String(taskpkg.Failed) + state.Tasks = append(state.Tasks, &taskState) group.State = taskpkg.Failed logger.Error("error executing task", zap.Error(err)) continue } logger.Debug("task execution completed successfully") + taskState.Status = ptr.String(string(task.State)) + state.Tasks = append(state.Tasks, &taskState) + // pass the response from current task as an input to the next task req = task.Response @@ -223,21 +254,29 @@ func (l *ListExecutor) executeSequential(ctx context.Context, group *tasklist.Gr group.State = taskpkg.Done } - return nil + state.ID = &group.ID + state.Status = ptr.String(string(group.State)) + + return &state, nil } -func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Group) error { +func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Group) (*goatasklist.GroupStatus, error) { group.State = taskpkg.Pending + var state goatasklist.GroupStatus tasks, err := l.storage.GetGroupTasks(ctx, group) if err != nil { - return err + return nil, err } var wg sync.WaitGroup for _, task := range tasks { wg.Add(1) go func(t *taskpkg.Task) { + taskState := goatasklist.TaskStatus{ + ID: &t.ID, + } + defer wg.Done() logger := l.logger.With( zap.String("taskID", t.ID), @@ -248,12 +287,17 @@ func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Grou if err := l.executeTask(ctx, t); err != nil { t.State = taskpkg.Failed + taskState.Status = ptr.String(taskpkg.Failed) + state.Tasks = append(state.Tasks, &taskState) group.State = taskpkg.Failed logger.Error("error executing task", zap.Error(err)) return } logger.Debug("task execution completed successfully") + taskState.Status = ptr.String(string(t.State)) + state.Tasks = append(state.Tasks, &taskState) + if err := l.cache.Set( ctx, t.ID, @@ -286,7 +330,10 @@ func (l *ListExecutor) executeParallel(ctx context.Context, group *tasklist.Grou group.State = taskpkg.Done } - return nil + state.ID = &group.ID + state.Status = ptr.String(string(group.State)) + + return &state, nil } func (l *ListExecutor) executeTask(ctx context.Context, task *taskpkg.Task) error { diff --git a/internal/service/tasklist/service.go b/internal/service/tasklist/service.go index 5542a7d24eace9e0d4782eea171151a4a46b4603..4356879964cb08ce18c8bcd5667332c77ebde735 100644 --- a/internal/service/tasklist/service.go +++ b/internal/service/tasklist/service.go @@ -1,6 +1,7 @@ package tasklist import ( + "bytes" "context" "encoding/json" "time" @@ -9,34 +10,45 @@ import ( "go.uber.org/zap" "code.vereign.com/gaiax/tsa/golib/errors" + "code.vereign.com/gaiax/tsa/golib/ptr" goatasklist "code.vereign.com/gaiax/tsa/task/gen/task_list" "code.vereign.com/gaiax/tsa/task/internal/service/task" ) //go:generate counterfeiter . Storage //go:generate counterfeiter . Queue +//go:generate counterfeiter . Cache // Storage for retrieving predefined task templates. type Storage interface { TaskListTemplate(ctx context.Context, taskListName string) (*Template, error) TaskTemplates(ctx context.Context, names []string) (map[string]*task.Task, error) + TaskList(ctx context.Context, taskListID string) (*TaskList, error) + TaskListHistory(ctx context.Context, taskListID string) (*TaskList, error) + GetGroupTasks(ctx context.Context, group *Group) ([]*task.Task, error) } type Queue interface { AddTaskList(ctx context.Context, taskList *TaskList, tasks []*task.Task) error } +type Cache interface { + Get(ctx context.Context, key, namespace, scope string) ([]byte, error) +} + type Service struct { storage Storage queue Queue + cache Cache logger *zap.Logger } -func New(template Storage, queue Queue, logger *zap.Logger) *Service { +func New(template Storage, queue Queue, cache Cache, logger *zap.Logger) *Service { return &Service{ storage: template, queue: queue, + cache: cache, logger: logger, } } @@ -105,6 +117,59 @@ func (s *Service) Create(ctx context.Context, req *goatasklist.CreateTaskListReq }, nil } +// TaskListResult retrieves a taskList result containing all tasks' unique IDs +// and statuses from the Cache service. +func (s *Service) TaskListResult(ctx context.Context, req *goatasklist.TaskListResultRequest) (res *goatasklist.TaskListStatus, err error) { + if req.TaskListID == "" { + return nil, errors.New(errors.BadRequest, "missing taskListID") + } + + logger := s.logger.With(zap.String("taskListID", req.TaskListID)) + + var list *TaskList + list, err = s.storage.TaskListHistory(ctx, req.TaskListID) + if err != nil && !errors.Is(errors.NotFound, err) { + logger.Error("error getting taskList from history collection", zap.Error(err)) + return nil, err + } + + if list == nil { + list, err = s.storage.TaskList(ctx, req.TaskListID) + if err != nil { + if errors.Is(errors.NotFound, err) { + return nil, errors.New("taskList is not found", err) + } + logger.Error("error getting taskList from taskLists collection", zap.Error(err)) + return nil, err + } + } + + var result *goatasklist.TaskListStatus + if list.State != task.Done && list.State != task.Failed { + // taskList is not executed yet + result, err = s.calculateState(ctx, list) + if err != nil { + logger.Error("error calculating taskList state", zap.Error(err)) + return nil, err + } + } else { + // taskList is already executed + var value []byte + value, err = s.cache.Get(ctx, list.ID, list.CacheNamespace, list.CacheScope) + if err != nil { + logger.Error("error getting taskList result from cache", zap.Error(err)) + return nil, err + } + + if err := json.NewDecoder(bytes.NewReader(value)).Decode(&result); err != nil { + logger.Error("error decoding result from cache", zap.Error(err)) + return nil, errors.New("error decoding result from cache", err) + } + } + + return result, nil +} + func createGroups(t *Template, req []byte) []Group { var groups []Group for _, group := range t.Groups { @@ -163,6 +228,36 @@ func createTasks(t *TaskList, templates map[string]*task.Task) ([]*task.Task, er return tasks, nil } +func (s *Service) calculateState(ctx context.Context, list *TaskList) (*goatasklist.TaskListStatus, error) { + result := &goatasklist.TaskListStatus{ + ID: list.ID, + Status: string(list.State), + } + + for i := range list.Groups { + groupState := goatasklist.GroupStatus{ + ID: &list.Groups[i].ID, + Status: ptr.String(string(list.Groups[i].State)), + } + + tasks, err := s.storage.GetGroupTasks(ctx, &list.Groups[i]) + if err != nil { + return nil, err + } + for j := range tasks { + taskState := goatasklist.TaskStatus{ + ID: &tasks[j].ID, + Status: ptr.String(string(tasks[j].State)), + } + groupState.Tasks = append(groupState.Tasks, &taskState) + } + + result.Groups = append(result.Groups, &groupState) + } + + return result, nil +} + // taskNamesFromTaskListTemplate returns the names of all tasks within // one taskList template func taskNamesFromTaskListTemplate(template *Template) []string { diff --git a/internal/service/tasklist/service_test.go b/internal/service/tasklist/service_test.go index 9dd3b39e39b25076f55f206661264627f2917c07..a14b0e1133d01ccda8cf485f3fa4ea79feac46f0 100644 --- a/internal/service/tasklist/service_test.go +++ b/internal/service/tasklist/service_test.go @@ -15,7 +15,7 @@ import ( ) func TestNew(t *testing.T) { - svc := tasklist.New(nil, nil, zap.NewNop()) + svc := tasklist.New(nil, nil, nil, zap.NewNop()) assert.Implements(t, (*goatasklist.Service)(nil), svc) } @@ -120,7 +120,7 @@ func Test_Create(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - svc := tasklist.New(test.storage, test.queue, zap.NewNop()) + svc := tasklist.New(test.storage, test.queue, nil, zap.NewNop()) res, err := svc.Create(context.Background(), test.req) if err != nil { assert.NotEmpty(t, test.errtext) @@ -138,3 +138,181 @@ func Test_Create(t *testing.T) { }) } } + +func Test_TaskListResult(t *testing.T) { + tests := []struct { + name string + req *goatasklist.TaskListResultRequest + storage *tasklistfakes.FakeStorage + queue *tasklistfakes.FakeQueue + cache *tasklistfakes.FakeCache + + errkind errors.Kind + errtext string + }{ + { + name: "missing taskList ID", + req: &goatasklist.TaskListResultRequest{}, + errkind: errors.BadRequest, + errtext: "missing taskListID", + }, + { + name: "error getting taskList form history collection", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return nil, errors.New("some error") + }, + }, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "taskList not found", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return nil, errors.New(errors.NotFound) + }, + TaskListStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return nil, errors.New(errors.NotFound) + }, + }, + errkind: errors.NotFound, + errtext: "taskList is not found", + }, + { + name: "error getting taskList from taskLists collection", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return nil, errors.New(errors.NotFound) + }, + TaskListStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return nil, errors.New("some error") + }, + }, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "error calculating taskList state", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return pendingTaskList, nil + }, + GetGroupTasksStub: func(ctx context.Context, group *tasklist.Group) ([]*task.Task, error) { + return nil, errors.New("some error") + }, + }, + errkind: errors.Unknown, + errtext: "some error", + }, + { + name: "error getting taskList from cache", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return doneTaskList, nil + }, + }, + cache: &tasklistfakes.FakeCache{ + GetStub: func(ctx context.Context, key, namespace, scope string) ([]byte, error) { + return nil, errors.New("some cache error") + }, + }, + errkind: errors.Unknown, + errtext: "some cache error", + }, + { + name: "successfully get taskList state on pending task", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return pendingTaskList, nil + }, + GetGroupTasksStub: func(ctx context.Context, group *tasklist.Group) ([]*task.Task, error) { + return []*task.Task{}, nil + }, + }, + }, + { + name: "successfully get taskList state on executed task", + req: &goatasklist.TaskListResultRequest{TaskListID: "d16996cd-1977-42a9-90b2-b4548a35c1b4"}, + storage: &tasklistfakes.FakeStorage{ + TaskListHistoryStub: func(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + return doneTaskList, nil + }, + }, + cache: &tasklistfakes.FakeCache{ + GetStub: func(ctx context.Context, key, namespace, scope string) ([]byte, error) { + return doneTaskState, nil + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + svc := tasklist.New(test.storage, test.queue, test.cache, zap.NewNop()) + res, err := svc.TaskListResult(context.Background(), test.req) + if err != nil { + assert.NotEmpty(t, test.errtext) + e, ok := err.(*errors.Error) + assert.True(t, ok) + assert.Equal(t, test.errkind, e.Kind) + assert.Contains(t, e.Error(), test.errtext) + assert.Nil(t, res) + } else { + assert.Empty(t, test.errtext) + assert.NotNil(t, res) + assert.NotEmpty(t, res.ID) + assert.NotEmpty(t, res.Status) + assert.NotEmpty(t, res.Groups) + } + }) + } +} + +//nolint:gosec +var pendingTaskList = &tasklist.TaskList{ + ID: "16996cd-1977-42a9-90b2-b4548a35c1b4", + State: "pending", + Groups: []tasklist.Group{ + { + ID: "074076d5-c995-4d2d-8d38-da57360453d4", + Tasks: []string{"createdTask", "createdTask2"}, + State: "created", + }, + }, +} + +//nolint:gosec +var doneTaskList = &tasklist.TaskList{ + ID: "16996cd-1977-42a9-90b2-b4548a35c1b4", + State: "done", +} + +//nolint:gosec +var doneTaskState = []byte(`{ + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done", + "groups": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "type": "sequential", + "status": "done", + "tasks": [ + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done" + }, + { + "id": "ad641603-1ca0-4342-ad73-d70a6b1ec502", + "status": "done" + } + ] + } + ] +}`) diff --git a/internal/service/tasklist/tasklistfakes/fake_cache.go b/internal/service/tasklist/tasklistfakes/fake_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..2f636bdb059b5e8cc84bd827b91bbc65e67a8d37 --- /dev/null +++ b/internal/service/tasklist/tasklistfakes/fake_cache.go @@ -0,0 +1,123 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package tasklistfakes + +import ( + "context" + "sync" + + "code.vereign.com/gaiax/tsa/task/internal/service/tasklist" +) + +type FakeCache struct { + GetStub func(context.Context, string, string, string) ([]byte, error) + getMutex sync.RWMutex + getArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getReturns struct { + result1 []byte + result2 error + } + getReturnsOnCall map[int]struct { + result1 []byte + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeCache) Get(arg1 context.Context, arg2 string, arg3 string, arg4 string) ([]byte, error) { + fake.getMutex.Lock() + ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)] + fake.getArgsForCall = append(fake.getArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + stub := fake.GetStub + fakeReturns := fake.getReturns + fake.recordInvocation("Get", []interface{}{arg1, arg2, arg3, arg4}) + fake.getMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeCache) GetCallCount() int { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + return len(fake.getArgsForCall) +} + +func (fake *FakeCache) GetCalls(stub func(context.Context, string, string, string) ([]byte, error)) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = stub +} + +func (fake *FakeCache) GetArgsForCall(i int) (context.Context, string, string, string) { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + argsForCall := fake.getArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeCache) GetReturns(result1 []byte, result2 error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + fake.getReturns = struct { + result1 []byte + result2 error + }{result1, result2} +} + +func (fake *FakeCache) GetReturnsOnCall(i int, result1 []byte, result2 error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + if fake.getReturnsOnCall == nil { + fake.getReturnsOnCall = make(map[int]struct { + result1 []byte + result2 error + }) + } + fake.getReturnsOnCall[i] = struct { + result1 []byte + result2 error + }{result1, result2} +} + +func (fake *FakeCache) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeCache) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ tasklist.Cache = new(FakeCache) diff --git a/internal/service/tasklist/tasklistfakes/fake_storage.go b/internal/service/tasklist/tasklistfakes/fake_storage.go index 9ece29b9180862d8281bd5759b4104ff9a731e67..4dd7cf097593ba40b96d8e9ea7731d96914d1a19 100644 --- a/internal/service/tasklist/tasklistfakes/fake_storage.go +++ b/internal/service/tasklist/tasklistfakes/fake_storage.go @@ -10,6 +10,48 @@ import ( ) type FakeStorage struct { + GetGroupTasksStub func(context.Context, *tasklist.Group) ([]*task.Task, error) + getGroupTasksMutex sync.RWMutex + getGroupTasksArgsForCall []struct { + arg1 context.Context + arg2 *tasklist.Group + } + getGroupTasksReturns struct { + result1 []*task.Task + result2 error + } + getGroupTasksReturnsOnCall map[int]struct { + result1 []*task.Task + result2 error + } + TaskListStub func(context.Context, string) (*tasklist.TaskList, error) + taskListMutex sync.RWMutex + taskListArgsForCall []struct { + arg1 context.Context + arg2 string + } + taskListReturns struct { + result1 *tasklist.TaskList + result2 error + } + taskListReturnsOnCall map[int]struct { + result1 *tasklist.TaskList + result2 error + } + TaskListHistoryStub func(context.Context, string) (*tasklist.TaskList, error) + taskListHistoryMutex sync.RWMutex + taskListHistoryArgsForCall []struct { + arg1 context.Context + arg2 string + } + taskListHistoryReturns struct { + result1 *tasklist.TaskList + result2 error + } + taskListHistoryReturnsOnCall map[int]struct { + result1 *tasklist.TaskList + result2 error + } TaskListTemplateStub func(context.Context, string) (*tasklist.Template, error) taskListTemplateMutex sync.RWMutex taskListTemplateArgsForCall []struct { @@ -42,6 +84,201 @@ type FakeStorage struct { invocationsMutex sync.RWMutex } +func (fake *FakeStorage) GetGroupTasks(arg1 context.Context, arg2 *tasklist.Group) ([]*task.Task, error) { + fake.getGroupTasksMutex.Lock() + ret, specificReturn := fake.getGroupTasksReturnsOnCall[len(fake.getGroupTasksArgsForCall)] + fake.getGroupTasksArgsForCall = append(fake.getGroupTasksArgsForCall, struct { + arg1 context.Context + arg2 *tasklist.Group + }{arg1, arg2}) + stub := fake.GetGroupTasksStub + fakeReturns := fake.getGroupTasksReturns + fake.recordInvocation("GetGroupTasks", []interface{}{arg1, arg2}) + fake.getGroupTasksMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) GetGroupTasksCallCount() int { + fake.getGroupTasksMutex.RLock() + defer fake.getGroupTasksMutex.RUnlock() + return len(fake.getGroupTasksArgsForCall) +} + +func (fake *FakeStorage) GetGroupTasksCalls(stub func(context.Context, *tasklist.Group) ([]*task.Task, error)) { + fake.getGroupTasksMutex.Lock() + defer fake.getGroupTasksMutex.Unlock() + fake.GetGroupTasksStub = stub +} + +func (fake *FakeStorage) GetGroupTasksArgsForCall(i int) (context.Context, *tasklist.Group) { + fake.getGroupTasksMutex.RLock() + defer fake.getGroupTasksMutex.RUnlock() + argsForCall := fake.getGroupTasksArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorage) GetGroupTasksReturns(result1 []*task.Task, result2 error) { + fake.getGroupTasksMutex.Lock() + defer fake.getGroupTasksMutex.Unlock() + fake.GetGroupTasksStub = nil + fake.getGroupTasksReturns = struct { + result1 []*task.Task + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) GetGroupTasksReturnsOnCall(i int, result1 []*task.Task, result2 error) { + fake.getGroupTasksMutex.Lock() + defer fake.getGroupTasksMutex.Unlock() + fake.GetGroupTasksStub = nil + if fake.getGroupTasksReturnsOnCall == nil { + fake.getGroupTasksReturnsOnCall = make(map[int]struct { + result1 []*task.Task + result2 error + }) + } + fake.getGroupTasksReturnsOnCall[i] = struct { + result1 []*task.Task + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskList(arg1 context.Context, arg2 string) (*tasklist.TaskList, error) { + fake.taskListMutex.Lock() + ret, specificReturn := fake.taskListReturnsOnCall[len(fake.taskListArgsForCall)] + fake.taskListArgsForCall = append(fake.taskListArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + stub := fake.TaskListStub + fakeReturns := fake.taskListReturns + fake.recordInvocation("TaskList", []interface{}{arg1, arg2}) + fake.taskListMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) TaskListCallCount() int { + fake.taskListMutex.RLock() + defer fake.taskListMutex.RUnlock() + return len(fake.taskListArgsForCall) +} + +func (fake *FakeStorage) TaskListCalls(stub func(context.Context, string) (*tasklist.TaskList, error)) { + fake.taskListMutex.Lock() + defer fake.taskListMutex.Unlock() + fake.TaskListStub = stub +} + +func (fake *FakeStorage) TaskListArgsForCall(i int) (context.Context, string) { + fake.taskListMutex.RLock() + defer fake.taskListMutex.RUnlock() + argsForCall := fake.taskListArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorage) TaskListReturns(result1 *tasklist.TaskList, result2 error) { + fake.taskListMutex.Lock() + defer fake.taskListMutex.Unlock() + fake.TaskListStub = nil + fake.taskListReturns = struct { + result1 *tasklist.TaskList + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskListReturnsOnCall(i int, result1 *tasklist.TaskList, result2 error) { + fake.taskListMutex.Lock() + defer fake.taskListMutex.Unlock() + fake.TaskListStub = nil + if fake.taskListReturnsOnCall == nil { + fake.taskListReturnsOnCall = make(map[int]struct { + result1 *tasklist.TaskList + result2 error + }) + } + fake.taskListReturnsOnCall[i] = struct { + result1 *tasklist.TaskList + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskListHistory(arg1 context.Context, arg2 string) (*tasklist.TaskList, error) { + fake.taskListHistoryMutex.Lock() + ret, specificReturn := fake.taskListHistoryReturnsOnCall[len(fake.taskListHistoryArgsForCall)] + fake.taskListHistoryArgsForCall = append(fake.taskListHistoryArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + stub := fake.TaskListHistoryStub + fakeReturns := fake.taskListHistoryReturns + fake.recordInvocation("TaskListHistory", []interface{}{arg1, arg2}) + fake.taskListHistoryMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStorage) TaskListHistoryCallCount() int { + fake.taskListHistoryMutex.RLock() + defer fake.taskListHistoryMutex.RUnlock() + return len(fake.taskListHistoryArgsForCall) +} + +func (fake *FakeStorage) TaskListHistoryCalls(stub func(context.Context, string) (*tasklist.TaskList, error)) { + fake.taskListHistoryMutex.Lock() + defer fake.taskListHistoryMutex.Unlock() + fake.TaskListHistoryStub = stub +} + +func (fake *FakeStorage) TaskListHistoryArgsForCall(i int) (context.Context, string) { + fake.taskListHistoryMutex.RLock() + defer fake.taskListHistoryMutex.RUnlock() + argsForCall := fake.taskListHistoryArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeStorage) TaskListHistoryReturns(result1 *tasklist.TaskList, result2 error) { + fake.taskListHistoryMutex.Lock() + defer fake.taskListHistoryMutex.Unlock() + fake.TaskListHistoryStub = nil + fake.taskListHistoryReturns = struct { + result1 *tasklist.TaskList + result2 error + }{result1, result2} +} + +func (fake *FakeStorage) TaskListHistoryReturnsOnCall(i int, result1 *tasklist.TaskList, result2 error) { + fake.taskListHistoryMutex.Lock() + defer fake.taskListHistoryMutex.Unlock() + fake.TaskListHistoryStub = nil + if fake.taskListHistoryReturnsOnCall == nil { + fake.taskListHistoryReturnsOnCall = make(map[int]struct { + result1 *tasklist.TaskList + result2 error + }) + } + fake.taskListHistoryReturnsOnCall[i] = struct { + result1 *tasklist.TaskList + result2 error + }{result1, result2} +} + func (fake *FakeStorage) TaskListTemplate(arg1 context.Context, arg2 string) (*tasklist.Template, error) { fake.taskListTemplateMutex.Lock() ret, specificReturn := fake.taskListTemplateReturnsOnCall[len(fake.taskListTemplateArgsForCall)] @@ -180,6 +417,12 @@ func (fake *FakeStorage) TaskTemplatesReturnsOnCall(i int, result1 map[string]*t func (fake *FakeStorage) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getGroupTasksMutex.RLock() + defer fake.getGroupTasksMutex.RUnlock() + fake.taskListMutex.RLock() + defer fake.taskListMutex.RUnlock() + fake.taskListHistoryMutex.RLock() + defer fake.taskListHistoryMutex.RUnlock() fake.taskListTemplateMutex.RLock() defer fake.taskListTemplateMutex.RUnlock() fake.taskTemplatesMutex.RLock() diff --git a/internal/storage/storage.go b/internal/storage/storage.go index cc16df1cd4392949218e17a91b7ab8995395864d..4a8d1ffaf7089f11273e316f45336238d833579e 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -315,3 +315,45 @@ func (s *Storage) SaveTaskListHistory(ctx context.Context, taskList *tasklist.Ta b := backoff.WithContext(backoff.NewExponentialBackOff(), ctx) return backoff.Retry(insert, b) } + +// TaskList retrieves a tasklist.TaskList from taskLists collection by ID +func (s *Storage) TaskList(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + result := s.taskLists.FindOne(ctx, bson.M{ + "id": taskListID, + }) + + if result.Err() != nil { + if strings.Contains(result.Err().Error(), "no documents in result") { + return nil, errors.New(errors.NotFound, "taskList not found") + } + return nil, result.Err() + } + + var list tasklist.TaskList + if err := result.Decode(&list); err != nil { + return nil, err + } + + return &list, nil +} + +// TaskListHistory retrieves a tasklist.TaskList from taskListHistory collection by ID +func (s *Storage) TaskListHistory(ctx context.Context, taskListID string) (*tasklist.TaskList, error) { + result := s.taskListHistory.FindOne(ctx, bson.M{ + "id": taskListID, + }) + + if result.Err() != nil { + if strings.Contains(result.Err().Error(), "no documents in result") { + return nil, errors.New(errors.NotFound, "taskList not found") + } + return nil, result.Err() + } + + var list tasklist.TaskList + if err := result.Decode(&list); err != nil { + return nil, err + } + + return &list, nil +}