draft test

This commit is contained in:
mr 2025-06-16 11:24:39 +02:00
parent 2a0ab8e549
commit 48299810e0
32 changed files with 1142 additions and 47 deletions

3
go.mod Normal file → Executable file
View File

@ -10,12 +10,13 @@ require (
github.com/nats-io/nats.go v1.37.0 github.com/nats-io/nats.go v1.37.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.10.0
) )
require ( require (
github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect
) )
require ( require (

4
go.sum Normal file → Executable file
View File

@ -106,9 +106,13 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=

View File

@ -21,7 +21,7 @@ import (
This package contains the models used in the application This package contains the models used in the application
It's used to create the models dynamically It's used to create the models dynamically
*/ */
var models = map[string]func() utils.DBObject{ var ModelsCatalog = map[string]func() utils.DBObject{
tools.WORKFLOW_RESOURCE.String(): func() utils.DBObject { return &resource.WorkflowResource{} }, tools.WORKFLOW_RESOURCE.String(): func() utils.DBObject { return &resource.WorkflowResource{} },
tools.DATA_RESOURCE.String(): func() utils.DBObject { return &resource.DataResource{} }, tools.DATA_RESOURCE.String(): func() utils.DBObject { return &resource.DataResource{} },
tools.COMPUTE_RESOURCE.String(): func() utils.DBObject { return &resource.ComputeResource{} }, tools.COMPUTE_RESOURCE.String(): func() utils.DBObject { return &resource.ComputeResource{} },
@ -43,8 +43,8 @@ var models = map[string]func() utils.DBObject{
// Model returns the model object based on the model type // Model returns the model object based on the model type
func Model(model int) utils.DBObject { func Model(model int) utils.DBObject {
log := logs.GetLogger() log := logs.GetLogger()
if _, ok := models[tools.FromInt(model)]; ok { if _, ok := ModelsCatalog[tools.FromInt(model)]; ok {
return models[tools.FromInt(model)]() return ModelsCatalog[tools.FromInt(model)]()
} }
log.Error().Msg("Can't find model " + tools.FromInt(model) + ".") log.Error().Msg("Can't find model " + tools.FromInt(model) + ".")
return nil return nil
@ -53,7 +53,7 @@ func Model(model int) utils.DBObject {
// GetModelsNames returns the names of the models // GetModelsNames returns the names of the models
func GetModelsNames() []string { func GetModelsNames() []string {
names := []string{} names := []string{}
for name := range models { for name := range ModelsCatalog {
names = append(names, name) names = append(names, name)
} }
return names return names

View File

@ -133,7 +133,7 @@ func (o *Order) draftStoreFromModel(scheduler *workflow_execution.WorkflowSchedu
o.SubOrders[peerOrder.GetID()] = peerOrder o.SubOrders[peerOrder.GetID()] = peerOrder
} }
// search an order with same user name and same session id // search an order with same user name and same session id
err := o.sumUpBill(request) err := o.SumUpBill(request)
if err != nil { if err != nil {
return err return err
} }
@ -196,7 +196,7 @@ func (d *Order) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor(request) // Create a new instance of the accessor return NewAccessor(request) // Create a new instance of the accessor
} }
func (d *Order) sumUpBill(request *tools.APIRequest) error { func (d *Order) SumUpBill(request *tools.APIRequest) error {
for _, b := range d.SubOrders { for _, b := range d.SubOrders {
err := b.SumUpBill(request) err := b.SumUpBill(request)
if err != nil { if err != nil {

View File

View File

@ -50,9 +50,9 @@ func (p *PeerCache) checkPeerStatus(peerID string, appName string) (*Peer, bool)
// LaunchPeerExecution launches an execution on a peer // LaunchPeerExecution launches an execution on a peer
// The method contacts the path described by : peer.Url + datatype path (from enums) + replacement of id by dataID // The method contacts the path described by : peer.Url + datatype path (from enums) + replacement of id by dataID
func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string, func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (*PeerExecution, error) { dt tools.DataType, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) (*PeerExecution, error) {
fmt.Println("Launching peer execution on", caller.URLS, dt, method) fmt.Println("Launching peer execution on", caller.GetUrls(), dt, method)
methods := caller.URLS[dt] // Get the methods url of the data type methods := caller.GetUrls()[dt] // Get the methods url of the data type
if m, ok := methods[method]; !ok || m == "" { if m, ok := methods[method]; !ok || m == "" {
return nil, errors.New("Requested method " + method.String() + " not declared in HTTPCaller") return nil, errors.New("Requested method " + method.String() + " not declared in HTTPCaller")
} }
@ -83,14 +83,14 @@ func (p *PeerCache) LaunchPeerExecution(peerID string, dataID string,
mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list mypeer.FailedExecution = []PeerExecution{} // Reset the failed executions list
NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db NewShallowAccessor().UpdateOne(mypeer, peerID) // Update the peer in the db
for _, v := range tmp { // Retry the failed executions for _, v := range tmp { // Retry the failed executions
go p.exec(v.Url, tools.ToMethod(v.Method), v.Body, caller) go p.Exec(v.Url, tools.ToMethod(v.Method), v.Body, caller)
} }
} }
return nil, p.exec(url, method, body, caller) // Execute the method return nil, p.Exec(url, method, body, caller) // Execute the method
} }
// exec executes the method on the peer // exec executes the method on the peer
func (p *PeerCache) exec(url string, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) error { func (p *PeerCache) Exec(url string, method tools.METHOD, body interface{}, caller tools.HTTPCallerITF) error {
var b []byte var b []byte
var err error var err error
if method == tools.POST { // Execute the POST method if it's a POST method if method == tools.POST { // Execute the POST method if it's a POST method

View File

@ -11,13 +11,13 @@ import (
type peerMongoAccessor struct { type peerMongoAccessor struct {
utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller) utils.AbstractAccessor // AbstractAccessor contains the basic fields of an accessor (model, caller)
overrideAuth bool OverrideAuth bool
} }
// New creates a new instance of the peerMongoAccessor // New creates a new instance of the peerMongoAccessor
func NewShallowAccessor() *peerMongoAccessor { func NewShallowAccessor() *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
overrideAuth: true, OverrideAuth: true,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
Type: tools.PEER, Type: tools.PEER,
@ -27,7 +27,7 @@ func NewShallowAccessor() *peerMongoAccessor {
func NewAccessor(request *tools.APIRequest) *peerMongoAccessor { func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
return &peerMongoAccessor{ return &peerMongoAccessor{
overrideAuth: false, OverrideAuth: false,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.PEER.String()), // Create a logger with the data type
Request: request, Request: request,
@ -37,7 +37,7 @@ func NewAccessor(request *tools.APIRequest) *peerMongoAccessor {
} }
func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool { func (wfa *peerMongoAccessor) ShouldVerifyAuth() bool {
return !wfa.overrideAuth return !wfa.OverrideAuth
} }
/* /*
@ -73,12 +73,12 @@ func (wfa *peerMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, in
} }
func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func (wfa *peerMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*Peer](filters, search, wfa.getDefaultFilter(search), return utils.GenericSearch[*Peer](filters, search, wfa.GetDefaultFilter(search),
func(d utils.DBObject) utils.ShallowDBObject { func(d utils.DBObject) utils.ShallowDBObject {
return d return d
}, isDraft, wfa) }, isDraft, wfa)
} }
func (a *peerMongoAccessor) getDefaultFilter(search string) *dbs.Filters { func (a *peerMongoAccessor) GetDefaultFilter(search string) *dbs.Filters {
if i, err := strconv.Atoi(search); err == nil { if i, err := strconv.Atoi(search); err == nil {
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // search by name if no filters are provided Or: map[string][]dbs.Filter{ // search by name if no filters are provided

View File

@ -0,0 +1,100 @@
package peer_test
import (
"encoding/json"
"testing"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockHTTPCaller mocks tools.HTTPCaller
type MockHTTPCaller struct {
mock.Mock
URLS map[tools.DataType]map[tools.METHOD]string
}
func (c *MockHTTPCaller) GetUrls() map[tools.DataType]map[tools.METHOD]string {
return c.URLS
}
func (m *MockHTTPCaller) CallPost(url, token string, body interface{}, types ...string) ([]byte, error) {
args := m.Called(url, token, body)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockHTTPCaller) CallGet(url, token string, types ...string) ([]byte, error) {
args := m.Called(url, token)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockHTTPCaller) CallDelete(url, token string) ([]byte, error) {
args := m.Called(url, token)
return args.Get(0).([]byte), args.Error(1)
}
func TestLaunchPeerExecution_PeerNotReachable(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{
URLS: map[tools.DataType]map[tools.METHOD]string{
tools.PEER: {
tools.POST: "/execute/:id",
},
},
}
exec, err := cache.LaunchPeerExecution("peer-id", "data-id", tools.PEER, tools.POST, map[string]string{"a": "b"}, caller)
assert.Nil(t, exec)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not reachable")
}
func TestExecSuccess(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
response := map[string]interface{}{"result": "ok"}
data, _ := json.Marshal(response)
caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.NoError(t, err)
caller.AssertExpectations(t)
}
func TestExecReturnsErrorField(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
response := map[string]interface{}{"error": "something failed"}
data, _ := json.Marshal(response)
caller.On("CallPost", url, "", mock.Anything).Return(data, nil)
err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.Error(t, err)
assert.Equal(t, "something failed", err.Error())
}
func TestExecInvalidJSON(t *testing.T) {
cache := &peer.PeerCache{}
caller := &MockHTTPCaller{}
url := "http://mockpeer/resource"
caller.On("CallPost", url, "", mock.Anything).Return([]byte("{invalid json}"), nil)
err := cache.Exec(url, tools.POST, map[string]string{"key": "value"}, caller)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid character")
}
type mockAccessor struct {
loadOne func(string) (interface{}, int, error)
updateOne func(interface{}, string) error
}
func (m *mockAccessor) LoadOne(id string) (interface{}, int, error) {
return m.loadOne(id)
}
func (m *mockAccessor) UpdateOne(i interface{}, id string) error {
return m.updateOne(i, id)
}

View File

@ -0,0 +1,127 @@
package peer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils"
)
type MockAccessor struct {
mock.Mock
utils.AbstractAccessor
}
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
args := m.Called(set, id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, search, isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func newTestPeer() *peer.Peer {
return &peer.Peer{
Url: "http://localhost",
WalletAddress: "0x123",
PublicKey: "pubkey",
State: peer.SELF,
}
}
func TestDeleteOne_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
mockAcc.On("DeleteOne", "id").Return(newTestPeer(), 200, nil)
obj, code, err := mockAcc.DeleteOne("id")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.NotNil(t, obj)
mockAcc.AssertExpectations(t)
}
func TestUpdateOne_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
peerObj := newTestPeer()
mockAcc.On("UpdateOne", peerObj, "id").Return(peerObj, 200, nil)
obj, code, err := mockAcc.UpdateOne(peerObj, "id")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, peerObj, obj)
mockAcc.AssertExpectations(t)
}
func TestStoreOne_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
peerObj := newTestPeer()
mockAcc.On("StoreOne", peerObj).Return(peerObj, 200, nil)
obj, code, err := mockAcc.StoreOne(peerObj)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, peerObj, obj)
mockAcc.AssertExpectations(t)
}
func TestLoadOne_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
mockAcc.On("LoadOne", "test-id").Return(newTestPeer(), 200, nil)
obj, code, err := mockAcc.LoadOne("test-id")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.NotNil(t, obj)
mockAcc.AssertExpectations(t)
}
func TestLoadAll_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
expected := []utils.ShallowDBObject{newTestPeer()}
mockAcc.On("LoadAll", false).Return(expected, 200, nil)
objs, code, err := mockAcc.LoadAll(false)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, expected, objs)
mockAcc.AssertExpectations(t)
}
func TestSearch_UsingMock(t *testing.T) {
mockAcc := new(MockAccessor)
filters := &dbs.Filters{}
expected := []utils.ShallowDBObject{newTestPeer()}
mockAcc.On("Search", filters, "test", false).Return(expected, 200, nil)
objs, code, err := mockAcc.Search(filters, "test", false)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, expected, objs)
mockAcc.AssertExpectations(t)
}

0
models/resources/compute.go Normal file → Executable file
View File

0
models/resources/data.go Normal file → Executable file
View File

0
models/resources/interfaces.go Normal file → Executable file
View File

0
models/resources/models.go Normal file → Executable file
View File

0
models/resources/priced_resource.go Normal file → Executable file
View File

0
models/resources/processing.go Normal file → Executable file
View File

0
models/resources/resource.go Normal file → Executable file
View File

0
models/resources/resource_accessor.go Normal file → Executable file
View File

0
models/resources/storage.go Normal file → Executable file
View File

0
models/resources/workflow.go Normal file → Executable file
View File

View File

@ -0,0 +1,38 @@
package models
import (
"strconv"
"testing"
"cloud.o-forge.io/core/oc-lib/models"
"github.com/stretchr/testify/assert"
)
func TestModel_ReturnsValidInstances(t *testing.T) {
for name, _ := range models.ModelsCatalog {
t.Run(name, func(t *testing.T) {
modelInt, _ := strconv.Atoi(name)
obj := models.Model(modelInt)
assert.NotNil(t, obj, "Model() returned nil for valid model name %s", name)
})
}
}
func TestModel_UnknownModelReturnsNil(t *testing.T) {
invalidModelInt := -9999 // unlikely to be valid
obj := models.Model(invalidModelInt)
assert.Nil(t, obj)
}
func TestGetModelsNames_ReturnsAllKeys(t *testing.T) {
names := models.GetModelsNames()
assert.Len(t, names, len(models.ModelsCatalog))
seen := make(map[string]bool)
for _, name := range names {
seen[name] = true
}
for key := range models.ModelsCatalog {
assert.Contains(t, seen, key)
}
}

0
models/utils/abstracts.go Normal file → Executable file
View File

0
models/utils/common.go Normal file → Executable file
View File

0
models/utils/interfaces.go Normal file → Executable file
View File

View File

@ -0,0 +1,128 @@
package models_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestGenerateID(t *testing.T) {
ao := &utils.AbstractObject{}
ao.GenerateID()
assert.NotEmpty(t, ao.UUID)
_, err := uuid.Parse(ao.UUID)
assert.NoError(t, err)
}
func TestStoreDraftDefault(t *testing.T) {
ao := &utils.AbstractObject{IsDraft: true}
ao.StoreDraftDefault()
assert.False(t, ao.IsDraft)
}
func TestCanUpdate(t *testing.T) {
ao := &utils.AbstractObject{}
res, set := ao.CanUpdate(nil)
assert.True(t, res)
assert.Nil(t, set)
}
func TestCanDelete(t *testing.T) {
ao := &utils.AbstractObject{}
assert.True(t, ao.CanDelete())
}
func TestIsDrafted(t *testing.T) {
ao := &utils.AbstractObject{IsDraft: true}
assert.True(t, ao.IsDrafted())
}
func TestGetID(t *testing.T) {
u := uuid.New().String()
ao := &utils.AbstractObject{UUID: u}
assert.Equal(t, u, ao.GetID())
}
func TestGetName(t *testing.T) {
name := "MyObject"
ao := &utils.AbstractObject{Name: name}
assert.Equal(t, name, ao.GetName())
}
func TestGetCreatorID(t *testing.T) {
id := "creator-123"
ao := &utils.AbstractObject{CreatorID: id}
assert.Equal(t, id, ao.GetCreatorID())
}
func TestUpToDate_CreateFalse(t *testing.T) {
ao := &utils.AbstractObject{}
now := time.Now()
time.Sleep(time.Millisecond) // ensure time difference
ao.UpToDate("user123", "peer456", false)
assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
assert.Equal(t, "peer456", ao.UpdaterID)
assert.Equal(t, "user123", ao.UserUpdaterID)
assert.True(t, ao.CreationDate.IsZero())
}
func TestUpToDate_CreateTrue(t *testing.T) {
ao := &utils.AbstractObject{}
now := time.Now()
time.Sleep(time.Millisecond)
ao.UpToDate("user123", "peer456", true)
assert.WithinDuration(t, now, ao.UpdateDate, time.Second)
assert.WithinDuration(t, now, ao.CreationDate, time.Second)
assert.Equal(t, "peer456", ao.UpdaterID)
assert.Equal(t, "peer456", ao.CreatorID)
assert.Equal(t, "user123", ao.UserUpdaterID)
assert.Equal(t, "user123", ao.UserCreatorID)
}
func TestVerifyAuth(t *testing.T) {
request := &tools.APIRequest{PeerID: "peer123"}
ao := &utils.AbstractObject{CreatorID: "peer123"}
assert.True(t, ao.VerifyAuth(request))
ao = &utils.AbstractObject{AccessMode: utils.Public}
assert.True(t, ao.VerifyAuth(nil))
ao = &utils.AbstractObject{AccessMode: utils.Private, CreatorID: "peer123"}
request = &tools.APIRequest{PeerID: "wrong"}
assert.False(t, ao.VerifyAuth(request))
}
func TestGetObjectFilters(t *testing.T) {
ao := &utils.AbstractObject{}
f := ao.GetObjectFilters("*")
assert.NotNil(t, f)
assert.Contains(t, f.Or, "abstractobject.name")
assert.Equal(t, dbs.LIKE.String(), f.Or["abstractobject.name"][0].Operator)
}
func TestDeserialize(t *testing.T) {
ao := &utils.AbstractObject{}
input := map[string]interface{}{"name": "test", "id": uuid.New().String()}
res := ao.Deserialize(input, &utils.AbstractObject{})
assert.NotNil(t, res)
}
func TestSerialize(t *testing.T) {
ao := &utils.AbstractObject{Name: "test", UUID: uuid.New().String()}
m := ao.Serialize(ao)
assert.Equal(t, "test", m["name"])
}
func TestAbstractAccessorMethods(t *testing.T) {
r := &utils.AbstractAccessor{Request: &tools.APIRequest{Username: "alice", PeerID: "peer1", Groups: []string{"dev"}}}
assert.True(t, r.ShouldVerifyAuth())
assert.Equal(t, "alice", r.GetUser())
assert.Equal(t, "peer1", r.GetPeerID())
assert.Equal(t, []string{"dev"}, r.GetGroups())
assert.Equal(t, r.Request.Caller, r.GetCaller())
}

View File

@ -0,0 +1,168 @@
package models_test
import (
"errors"
"testing"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// --- Mock Definitions ---
type MockDBObject struct {
mock.Mock
}
func (m *MockAccessor) GetLogger() *zerolog.Logger {
return nil
}
func (m *MockAccessor) GetGroups() []string {
return []string{}
}
func (m *MockAccessor) GetCaller() *tools.HTTPCaller {
return nil
}
func (m *MockDBObject) GenerateID() { m.Called() }
func (m *MockDBObject) StoreDraftDefault() { m.Called() }
func (m *MockDBObject) UpToDate(user, peer string, create bool) {
m.Called(user, peer, create)
}
func (m *MockDBObject) VerifyAuth(req *tools.APIRequest) bool {
args := m.Called(req)
return args.Bool(0)
}
func (m *MockDBObject) CanDelete() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockDBObject) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
args := m.Called(set)
return args.Bool(0), args.Get(1).(utils.DBObject)
}
func (m *MockDBObject) IsDrafted() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockDBObject) Serialize(obj utils.DBObject) map[string]interface{} {
args := m.Called(obj)
return args.Get(0).(map[string]interface{})
}
func (m *MockDBObject) Deserialize(mdata map[string]interface{}, obj utils.DBObject) utils.DBObject {
args := m.Called(mdata, obj)
return args.Get(0).(utils.DBObject)
}
func (m *MockDBObject) GetID() string {
args := m.Called()
return args.String(0)
}
func (m *MockDBObject) GetName() string {
args := m.Called()
return args.String(0)
}
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
args := m.Called(set, id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) ShouldVerifyAuth() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockAccessor) GetRequest() *tools.APIRequest {
args := m.Called()
return args.Get(0).(*tools.APIRequest)
}
func (m *MockAccessor) GetType() tools.DataType {
args := m.Called()
return args.Get(0).(tools.DataType)
}
func (m *MockAccessor) Search(filters *dbs.Filters, s string, d bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, s, d)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockAccessor) GetUser() string {
args := m.Called()
return args.String(0)
}
func (m *MockAccessor) GetPeerID() string {
args := m.Called()
return args.String(0)
}
// --- Test Cases ---
func TestVerifyAccess_Authorized(t *testing.T) {
mockObj := new(MockDBObject)
mockAcc := new(MockAccessor)
req := &tools.APIRequest{PeerID: "peer"}
mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
mockAcc.On("ShouldVerifyAuth").Return(true)
mockObj.On("VerifyAuth", req).Return(true)
mockAcc.On("GetRequest").Return(req)
err := utils.VerifyAccess(mockAcc, "123")
assert.NoError(t, err)
}
func TestVerifyAccess_Unauthorized(t *testing.T) {
mockObj := new(MockDBObject)
mockAcc := new(MockAccessor)
req := &tools.APIRequest{PeerID: "peer"}
mockAcc.On("LoadOne", "123").Return(mockObj, 200, nil)
mockAcc.On("ShouldVerifyAuth").Return(true)
mockObj.On("VerifyAuth", req).Return(false)
mockAcc.On("GetRequest").Return(req)
err := utils.VerifyAccess(mockAcc, "123")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not allowed")
}
func TestVerifyAccess_LoadError(t *testing.T) {
mockAcc := new(MockAccessor)
mockAcc.On("LoadOne", "bad-id").Return(nil, 404, errors.New("not found"))
err := utils.VerifyAccess(mockAcc, "bad-id")
assert.Error(t, err)
assert.Equal(t, "not found", err.Error())
}

View File

@ -0,0 +1,149 @@
package workflow_execution_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockAccessor struct {
mock.Mock
}
func (m *MockAccessor) LoadOne(id string) (interface{}, int, error) {
args := m.Called(id)
return args.Get(0), args.Int(1), args.Error(2)
}
func TestNewScheduler_ValidInput(t *testing.T) {
s := "2025-06-16T15:00:00"
e := "2025-06-16T17:00:00"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.NotNil(t, sched)
assert.Equal(t, dur, sched.DurationS)
assert.Equal(t, cronStr, sched.Cron)
}
func TestNewScheduler_InvalidStart(t *testing.T) {
s := "invalid"
e := "2025-06-16T17:00:00"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.Nil(t, sched)
}
func TestNewScheduler_InvalidEnd(t *testing.T) {
s := "2025-06-16T15:00:00"
e := "invalid"
dur := 7200.0
cronStr := "0 0 * * * *"
sched := workflow_execution.NewScheduler(s, e, dur, cronStr)
assert.NotNil(t, sched)
assert.Nil(t, sched.End)
}
func TestGetDates_NoCron(t *testing.T) {
start := time.Now()
end := start.Add(2 * time.Hour)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
}
schedule, err := s.GetDates()
assert.NoError(t, err)
assert.Len(t, schedule, 1)
assert.Equal(t, start, schedule[0].Start)
assert.Equal(t, end, *schedule[0].End)
}
func TestGetDates_InvalidCron(t *testing.T) {
start := time.Now()
end := start.Add(2 * time.Hour)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
Cron: "bad cron",
}
_, err := s.GetDates()
assert.Error(t, err)
}
func TestGetDates_ValidCron(t *testing.T) {
start := time.Now()
end := start.Add(10 * time.Minute)
s := &workflow_execution.WorkflowSchedule{
Start: start,
End: &end,
DurationS: 60,
Cron: "0 */2 * * * *",
}
dates, err := s.GetDates()
assert.NoError(t, err)
assert.Greater(t, len(dates), 0)
}
func TestGetExecutions_Success(t *testing.T) {
start := time.Now()
end := start.Add(1 * time.Hour)
ws := &workflow_execution.WorkflowSchedule{
UUID: uuid.New().String(),
Start: start,
End: &end,
}
wf := &workflow.Workflow{
AbstractObject: utils.AbstractObject{
UUID: uuid.New().String(),
Name: "TestWorkflow",
},
}
execs, err := ws.GetExecutions(wf)
assert.NoError(t, err)
assert.Greater(t, len(execs), 0)
assert.Equal(t, wf.UUID, execs[0].WorkflowID)
assert.Equal(t, ws.UUID, execs[0].ExecutionsID)
assert.Equal(t, enum.DRAFT, execs[0].State)
}
func TestSchedules_NoRequest(t *testing.T) {
ws := &workflow_execution.WorkflowSchedule{}
ws, wf, execs, err := ws.Schedules("someID", nil)
assert.Error(t, err)
assert.Nil(t, wf)
assert.Len(t, execs, 0)
assert.Equal(t, ws, ws)
}
// Additional test stubs to be completed with gomock usage for:
// - CheckBooking
// - BookExecs
// - getBooking
// - Schedules (success path)
// - Planify mocking in CheckBooking
// - Peer interaction in BookExecs
// - Caller deep copy errors in getCallerCopy
// Will be continued...

View File

@ -0,0 +1,154 @@
package workflow_execution_test
import (
"testing"
"time"
"cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
"cloud.o-forge.io/core/oc-lib/tools"
"github.com/stretchr/testify/assert"
)
func (m *MockAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return nil, args.Int(1), args.Error(2)
}
func (m *MockAccessor) Search(filters interface{}, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, search, isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func TestStoreDraftDefault(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.StoreDraftDefault()
assert.False(t, exec.IsDraft)
assert.Equal(t, enum.SCHEDULED, exec.State)
}
func TestCanUpdate_StateChange(t *testing.T) {
existing := &workflow_execution.WorkflowExecution{State: enum.DRAFT}
newExec := &workflow_execution.WorkflowExecution{State: enum.SCHEDULED}
canUpdate, updated := existing.CanUpdate(newExec)
assert.True(t, canUpdate)
assert.Equal(t, enum.SCHEDULED, updated.(*workflow_execution.WorkflowExecution).State)
}
func TestCanUpdate_SameState_Draft(t *testing.T) {
existing := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
newExec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}, State: enum.DRAFT}
canUpdate, _ := existing.CanUpdate(newExec)
assert.False(t, canUpdate)
}
func TestCanDelete_TrueIfDraft(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: true}}
assert.True(t, exec.CanDelete())
}
func TestCanDelete_FalseIfNotDraft(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{IsDraft: false}}
assert.False(t, exec.CanDelete())
}
func TestEquals_True(t *testing.T) {
d := time.Now()
exec1 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
exec2 := &workflow_execution.WorkflowExecution{ExecDate: d, WorkflowID: "123"}
assert.True(t, exec1.Equals(exec2))
}
func TestEquals_False(t *testing.T) {
exec1 := &workflow_execution.WorkflowExecution{ExecDate: time.Now(), WorkflowID: "abc"}
exec2 := &workflow_execution.WorkflowExecution{ExecDate: time.Now().Add(time.Hour), WorkflowID: "def"}
assert.False(t, exec1.Equals(exec2))
}
func TestArgoStatusToState_Success(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.ArgoStatusToState("succeeded")
assert.Equal(t, enum.SUCCESS, exec.State)
}
func TestArgoStatusToState_DefaultToFailure(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.ArgoStatusToState("unknown")
assert.Equal(t, enum.FAILURE, exec.State)
}
func TestGenerateID_AssignsUUID(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
exec.GenerateID()
assert.NotEmpty(t, exec.UUID)
}
func TestGetName_ReturnsCorrectFormat(t *testing.T) {
time := time.Now()
exec := &workflow_execution.WorkflowExecution{AbstractObject: utils.AbstractObject{UUID: "abc"}, ExecDate: time}
assert.Contains(t, exec.GetName(), "abc")
assert.Contains(t, exec.GetName(), time.String())
}
func TestVerifyAuth_AlwaysTrue(t *testing.T) {
exec := &workflow_execution.WorkflowExecution{}
assert.True(t, exec.VerifyAuth(nil))
}
func TestUpdateOne_RejectsZeroState(t *testing.T) {
accessor := &workflow_execution.WorkflowExecutionMongoAccessor{}
set := &workflow_execution.WorkflowExecution{State: 0}
_, code, err := accessor.UpdateOne(set, "someID")
assert.Equal(t, 400, code)
assert.Error(t, err)
}
func TestLoadOne_DraftExpired_ShouldDelete(t *testing.T) {
// Normally would mock time.Now and delete call; for now we test structure
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
exec := &workflow_execution.WorkflowExecution{
ExecDate: time.Now().Add(-2 * time.Minute),
State: enum.DRAFT,
AbstractObject: utils.AbstractObject{UUID: "to-delete"},
}
_, _, _ = accessor.LoadOne(exec.GetID())
// No panic = good enough placeholder
}
func TestLoadOne_ScheduledExpired_ShouldUpdateToForgotten(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
exec := &workflow_execution.WorkflowExecution{
ExecDate: time.Now().Add(-2 * time.Minute),
State: enum.SCHEDULED,
AbstractObject: utils.AbstractObject{UUID: "to-forget"},
}
_, _, _ = accessor.LoadOne(exec.GetID())
}
func TestDeleteOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.DeleteOne("someID")
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestStoreOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.StoreOne(nil)
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestCopyOne_NotImplemented(t *testing.T) {
accessor := workflow_execution.NewAccessor(&tools.APIRequest{})
_, code, err := accessor.CopyOne(nil)
assert.Equal(t, 404, code)
assert.Error(t, err)
}
func TestGetExecFilters_BasicPattern(t *testing.T) {
a := workflow_execution.NewAccessor(&tools.APIRequest{})
filters := a.GetExecFilters("foo")
assert.Contains(t, filters.Or["abstractobject.name"][0].Value, "foo")
}

0
models/workflow_execution/workflow_execution.go Normal file → Executable file
View File

View File

@ -11,13 +11,13 @@ import (
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type workflowExecutionMongoAccessor struct { type WorkflowExecutionMongoAccessor struct {
utils.AbstractAccessor utils.AbstractAccessor
shallow bool shallow bool
} }
func newShallowAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor { func newShallowAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
return &workflowExecutionMongoAccessor{ return &WorkflowExecutionMongoAccessor{
shallow: true, shallow: true,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
@ -27,8 +27,8 @@ func newShallowAccessor(request *tools.APIRequest) *workflowExecutionMongoAccess
} }
} }
func NewAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor { func NewAccessor(request *tools.APIRequest) *WorkflowExecutionMongoAccessor {
return &workflowExecutionMongoAccessor{ return &WorkflowExecutionMongoAccessor{
shallow: false, shallow: false,
AbstractAccessor: utils.AbstractAccessor{ AbstractAccessor: utils.AbstractAccessor{
Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type Logger: logs.CreateLogger(tools.WORKFLOW_EXECUTION.String()), // Create a logger with the data type
@ -38,11 +38,11 @@ func NewAccessor(request *tools.APIRequest) *workflowExecutionMongoAccessor {
} }
} }
func (wfa *workflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) { func (wfa *WorkflowExecutionMongoAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented") return nil, 404, errors.New("not implemented")
} }
func (wfa *workflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) { func (wfa *WorkflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
if set.(*WorkflowExecution).State == 0 { if set.(*WorkflowExecution).State == 0 {
return nil, 400, errors.New("state is required") return nil, 400, errors.New("state is required")
} }
@ -50,15 +50,15 @@ func (wfa *workflowExecutionMongoAccessor) UpdateOne(set utils.DBObject, id stri
return utils.GenericUpdateOne(&realSet, id, wfa, &WorkflowExecution{}) return utils.GenericUpdateOne(&realSet, id, wfa, &WorkflowExecution{})
} }
func (wfa *workflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) { func (wfa *WorkflowExecutionMongoAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented") return nil, 404, errors.New("not implemented")
} }
func (wfa *workflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) { func (wfa *WorkflowExecutionMongoAccessor) CopyOne(data utils.DBObject) (utils.DBObject, int, error) {
return nil, 404, errors.New("not implemented") return nil, 404, errors.New("not implemented")
} }
func (a *workflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) { func (a *WorkflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int, error) {
return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) { return utils.GenericLoadOne[*WorkflowExecution](id, func(d utils.DBObject) (utils.DBObject, int, error) {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)
@ -74,15 +74,15 @@ func (a *workflowExecutionMongoAccessor) LoadOne(id string) (utils.DBObject, int
}, a) }, a)
} }
func (a *workflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) { func (a *WorkflowExecutionMongoAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), isDraft, a) return utils.GenericLoadAll[*WorkflowExecution](a.getExec(), isDraft, a)
} }
func (a *workflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) { func (a *WorkflowExecutionMongoAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
return utils.GenericSearch[*WorkflowExecution](filters, search, a.GetExecFilters(search), a.getExec(), isDraft, a) return utils.GenericSearch[*WorkflowExecution](filters, search, a.GetExecFilters(search), a.getExec(), isDraft, a)
} }
func (a *workflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject { func (a *WorkflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.ShallowDBObject {
return func(d utils.DBObject) utils.ShallowDBObject { return func(d utils.DBObject) utils.ShallowDBObject {
now := time.Now() now := time.Now()
now = now.Add(time.Second * -60) now = now.Add(time.Second * -60)
@ -99,7 +99,7 @@ func (a *workflowExecutionMongoAccessor) getExec() func(utils.DBObject) utils.Sh
} }
} }
func (a *workflowExecutionMongoAccessor) GetExecFilters(search string) *dbs.Filters { func (a *WorkflowExecutionMongoAccessor) GetExecFilters(search string) *dbs.Filters {
return &dbs.Filters{ return &dbs.Filters{
Or: map[string][]dbs.Filter{ // filter by name if no filters are provided Or: map[string][]dbs.Filter{ // filter by name if no filters are provided
"abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}}, "abstractobject.name": {{Operator: dbs.LIKE.String(), Value: search + "_execution"}},

16
models/workflow_execution/workflow_scheduler.go Normal file → Executable file
View File

@ -71,7 +71,7 @@ func (ws *WorkflowSchedule) CheckBooking(wfID string, request *tools.APIRequest)
if ws.End != nil && ws.Start.Add(time.Duration(longest)*time.Second).After(*ws.End) { if ws.End != nil && ws.Start.Add(time.Duration(longest)*time.Second).After(*ws.End) {
ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n" ws.Warning = "The workflow may be too long to be executed in the given time frame, we will try to book it anyway\n"
} }
execs, err := ws.getExecutions(wf) execs, err := ws.GetExecutions(wf)
if err != nil { if err != nil {
return false, wf, []*WorkflowExecution{}, []*booking.Booking{}, err return false, wf, []*WorkflowExecution{}, []*booking.Booking{}, err
} }
@ -96,7 +96,7 @@ func (ws *WorkflowSchedule) CheckBooking(wfID string, request *tools.APIRequest)
return true, wf, execs, bookings, nil return true, wf, execs, bookings, nil
} }
func getBooking( b *booking.Booking, request *tools.APIRequest, wf *workflow.Workflow, execs []*WorkflowExecution, bookings []*booking.Booking, errCh chan error, m *sync.Mutex) { func getBooking(b *booking.Booking, request *tools.APIRequest, wf *workflow.Workflow, execs []*WorkflowExecution, bookings []*booking.Booking, errCh chan error, m *sync.Mutex) {
m.Lock() m.Lock()
c, err := getCallerCopy(request, errCh) c, err := getCallerCopy(request, errCh)
@ -157,9 +157,9 @@ func (ws *WorkflowSchedule) Schedules(wfID string, request *tools.APIRequest) (*
for _, booking := range bookings { for _, booking := range bookings {
go ws.BookExecs(booking, request, errCh, &m) go ws.BookExecs(booking, request, errCh, &m)
} }
for i := 0; i < len(bookings); i++ { for i := 0; i < len(bookings); i++ {
if err := <- errCh ; err != nil { if err := <-errCh; err != nil {
return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err)) return ws, wf, executions, errors.New("could not launch the peer execution : " + fmt.Sprintf("%v", err))
} }
} }
@ -189,7 +189,7 @@ func (ws *WorkflowSchedule) BookExecs(booking *booking.Booking, request *tools.A
_, err = (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "", _, err = (&peer.Peer{}).LaunchPeerExecution(booking.DestPeerID, "",
tools.BOOKING, tools.POST, booking.Serialize(booking), &c) tools.BOOKING, tools.POST, booking.Serialize(booking), &c)
if err != nil { if err != nil {
errCh <- err errCh <- err
return return
@ -210,9 +210,9 @@ VERIFY THAT WE HANDLE DIFFERENCE BETWEEN LOCATION TIME && BOOKING
* getExecutions is a function that returns the executions of a workflow * getExecutions is a function that returns the executions of a workflow
* it returns an array of workflow_execution.WorkflowExecution * it returns an array of workflow_execution.WorkflowExecution
*/ */
func (ws *WorkflowSchedule) getExecutions(workflow *workflow.Workflow) ([]*WorkflowExecution, error) { func (ws *WorkflowSchedule) GetExecutions(workflow *workflow.Workflow) ([]*WorkflowExecution, error) {
workflows_executions := []*WorkflowExecution{} workflows_executions := []*WorkflowExecution{}
dates, err := ws.getDates() dates, err := ws.GetDates()
if err != nil { if err != nil {
return workflows_executions, err return workflows_executions, err
} }
@ -233,7 +233,7 @@ func (ws *WorkflowSchedule) getExecutions(workflow *workflow.Workflow) ([]*Workf
return workflows_executions, nil return workflows_executions, nil
} }
func (ws *WorkflowSchedule) getDates() ([]Schedule, error) { func (ws *WorkflowSchedule) GetDates() ([]Schedule, error) {
schedule := []Schedule{} schedule := []Schedule{}
if len(ws.Cron) > 0 { // if cron is set then end date should be set if len(ws.Cron) > 0 { // if cron is set then end date should be set
if ws.End == nil { if ws.End == nil {

View File

@ -0,0 +1,215 @@
// File: workspace_accessor_test.go
package workspace_test
import (
"errors"
"testing"
"cloud.o-forge.io/core/oc-lib/dbs"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/models/workspace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockWorkspaceAccessor struct {
mock.Mock
workspace.Workspace
}
func (m *MockWorkspaceAccessor) StoreOne(data utils.DBObject) (utils.DBObject, int, error) {
args := m.Called(data)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockWorkspaceAccessor) UpdateOne(set utils.DBObject, id string) (utils.DBObject, int, error) {
args := m.Called(set, id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockWorkspaceAccessor) DeleteOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockWorkspaceAccessor) LoadOne(id string) (utils.DBObject, int, error) {
args := m.Called(id)
return args.Get(0).(utils.DBObject), args.Int(1), args.Error(2)
}
func (m *MockWorkspaceAccessor) LoadAll(isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func (m *MockWorkspaceAccessor) Search(filters *dbs.Filters, search string, isDraft bool) ([]utils.ShallowDBObject, int, error) {
args := m.Called(filters, search, isDraft)
return args.Get(0).([]utils.ShallowDBObject), args.Int(1), args.Error(2)
}
func TestStoreOne_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "test_ws"}}
mockAcc.On("StoreOne", ws).Return(ws, 200, nil)
res, code, err := mockAcc.StoreOne(ws)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, ws, res)
mockAcc.AssertExpectations(t)
}
func TestStoreOne_Conflict(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{Name: "duplicate"}}
mockAcc.On("StoreOne", ws).Return(nil, 409, errors.New("a workspace with the same name already exists"))
res, code, err := mockAcc.StoreOne(ws)
assert.Error(t, err)
assert.Equal(t, 409, code)
assert.Nil(t, res)
}
func TestUpdateOne_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "123", IsDraft: false}}
mockAcc.On("UpdateOne", ws, "123").Return(ws, 200, nil)
res, code, err := mockAcc.UpdateOne(ws, "123")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, ws, res)
}
func TestUpdateOne_Error(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "999"}}
err := errors.New("update failed")
mockAcc.On("UpdateOne", ws, "999").Return(nil, 500, err)
res, code, err := mockAcc.UpdateOne(ws, "999")
assert.Error(t, err)
assert.Equal(t, 500, code)
assert.Nil(t, res)
}
func TestDeleteOne_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "321"}}
mockAcc.On("DeleteOne", "321").Return(ws, 200, nil)
res, code, err := mockAcc.DeleteOne("321")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, ws, res)
}
func TestDeleteOne_NotFound(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
err := errors.New("not found")
mockAcc.On("DeleteOne", "notfound").Return(nil, 404, err)
res, code, err := mockAcc.DeleteOne("notfound")
assert.Error(t, err)
assert.Equal(t, 404, code)
assert.Nil(t, res)
}
func TestLoadOne_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "loadid"}}
mockAcc.On("LoadOne", "loadid").Return(ws, 200, nil)
res, code, err := mockAcc.LoadOne("loadid")
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Equal(t, ws, res)
}
func TestLoadOne_Error(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
err := errors.New("db error")
mockAcc.On("LoadOne", "badid").Return(nil, 500, err)
res, code, err := mockAcc.LoadOne("badid")
assert.Error(t, err)
assert.Equal(t, 500, code)
assert.Nil(t, res)
}
func TestLoadAll_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
ws := &workspace.Workspace{AbstractObject: utils.AbstractObject{UUID: "all1"}}
mockAcc.On("LoadAll", true).Return([]utils.ShallowDBObject{ws}, 200, nil)
res, code, err := mockAcc.LoadAll(true)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Len(t, res, 1)
}
func TestLoadAll_Empty(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
mockAcc.On("LoadAll", false).Return([]utils.ShallowDBObject{}, 200, nil)
res, code, err := mockAcc.LoadAll(false)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.Empty(t, res)
}
func TestSearch_Success(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
filters := &dbs.Filters{}
mockAcc.On("Search", filters, "keyword", true).Return([]utils.ShallowDBObject{}, 200, nil)
res, code, err := mockAcc.Search(filters, "keyword", true)
assert.NoError(t, err)
assert.Equal(t, 200, code)
assert.NotNil(t, res)
}
func TestSearch_Error(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
filters := &dbs.Filters{}
err := errors.New("search failed")
mockAcc.On("Search", filters, "fail", false).Return(nil, 500, err)
res, code, err := mockAcc.Search(filters, "fail", false)
assert.Error(t, err)
assert.Equal(t, 500, code)
assert.Nil(t, res)
}
// Additional edge test cases
func TestStoreOne_InvalidType(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
mockAcc.On("StoreOne", mock.Anything).Return(nil, 400, errors.New("invalid type"))
res, code, err := mockAcc.StoreOne(&utils.AbstractObject{})
assert.Error(t, err)
assert.Equal(t, 400, code)
assert.Nil(t, res)
}
func TestUpdateOne_NilData(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
mockAcc.On("UpdateOne", nil, "id").Return(nil, 400, errors.New("nil data"))
res, code, err := mockAcc.UpdateOne(nil, "id")
assert.Error(t, err)
assert.Equal(t, 400, code)
assert.Nil(t, res)
}
func TestDeleteOne_NilID(t *testing.T) {
mockAcc := new(MockWorkspaceAccessor)
mockAcc.On("DeleteOne", "").Return(nil, 400, errors.New("missing ID"))
res, code, err := mockAcc.DeleteOne("")
assert.Error(t, err)
assert.Equal(t, 400, code)
assert.Nil(t, res)
}

View File

@ -47,12 +47,19 @@ func ToMethod(str string) METHOD {
return GET return GET
} }
type HTTPCallerITF interface {
GetUrls() map[DataType]map[METHOD]string
CallGet(url string, subpath string, types ...string) ([]byte, error)
CallPost(url string, subpath string, body interface{}, types ...string) ([]byte, error)
CallDelete(url string, subpath string) ([]byte, error)
}
var HTTPCallerInstance = &HTTPCaller{} // Singleton instance of the HTTPCaller var HTTPCallerInstance = &HTTPCaller{} // Singleton instance of the HTTPCaller
type HTTPCaller struct { type HTTPCaller struct {
URLS map[DataType]map[METHOD]string // Map of the different methods and their urls URLS map[DataType]map[METHOD]string // Map of the different methods and their urls
Disabled bool // Disabled flag Disabled bool // Disabled flag
LastResults map[string]interface{} // Used to store information regarding the last execution of a given method on a given data type LastResults map[string]interface{} // Used to store information regarding the last execution of a given method on a given data type
} }
// NewHTTPCaller creates a new instance of the HTTP Caller // NewHTTPCaller creates a new instance of the HTTP Caller
@ -63,8 +70,12 @@ func NewHTTPCaller(urls map[DataType]map[METHOD]string) *HTTPCaller {
} }
} }
// Creates a copy of the current caller, in order to have parallelized executions without race condition func (c *HTTPCaller) GetUrls() map[DataType]map[METHOD]string {
func (c* HTTPCaller) DeepCopy(dst HTTPCaller) error { return c.URLS
}
// Creates a copy of the current caller, in order to have parallelized executions without race condition
func (c *HTTPCaller) DeepCopy(dst HTTPCaller) error {
bytes, err := json.Marshal(c) bytes, err := json.Marshal(c)
if err != nil { if err != nil {
return err return err
@ -219,4 +230,4 @@ func (caller *HTTPCaller) StoreResp(resp *http.Response) error {
caller.LastResults["body"] = data caller.LastResults["body"] = data
return nil return nil
} }