package testsuite import ( "context" "encoding/json" "reflect" "testing" "time" "forge.cadoles.com/arcad/edge/pkg/app" "forge.cadoles.com/arcad/edge/pkg/storage/share" "github.com/pkg/errors" ) type repositoryTestCase struct { Name string Skip bool Run func(ctx context.Context, t *testing.T, store share.Store) error } var repositoryTestCases = []repositoryTestCase{ { Name: "Update resource attributes", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { origin := app.ID("test") resourceID := share.ResourceID("test") // Try to create resource without attributes _, err := store.UpdateAttributes(ctx, origin, resourceID) if err == nil { return errors.New("err should not be nil") } if !errors.Is(err, share.ErrAttributeRequired) { return errors.Errorf("err: expected share.ErrAttributeRequired, got '%+v'", err) } attributes := []share.Attribute{ share.NewBaseAttribute("my_text_attr", share.TypeText, "foo"), share.NewBaseAttribute("my_number_attr", share.TypeNumber, 5), share.NewBaseAttribute("my_path_attr", share.TypePath, "/my/path"), share.NewBaseAttribute("my_bool_attr", share.TypeBool, true), } resource, err := store.UpdateAttributes(ctx, origin, resourceID, attributes...) if err != nil { return errors.WithStack(err) } isNil := reflect.ValueOf(resource).IsNil() if isNil { return errors.New("resource should not be nil") } if e, g := resourceID, resource.ID(); e != g { return errors.Errorf("resource.ID(): expected '%v', got '%v'", e, g) } if e, g := origin, resource.Origin(); e != g { return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g) } if e, g := 4, len(resource.Attributes()); e != g { return errors.Errorf("len(resource.Attributes()): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Find resources by attribute name", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/find_resources_by_attribute_name.json", store); err != nil { return errors.WithStack(err) } resources, err := store.FindResources(ctx, share.WithName("my_number")) if err != nil { return errors.WithStack(err) } isNil := reflect.ValueOf(resources).IsNil() if isNil { return errors.New("resources should not be nil") } if e, g := 2, len(resources); e != g { return errors.Errorf("len(resources): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Find resources by attribute type", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/find_resources_by_attribute_type.json", store); err != nil { return errors.WithStack(err) } resources, err := store.FindResources(ctx, share.WithType(share.TypePath)) if err != nil { return errors.WithStack(err) } isNil := reflect.ValueOf(resources).IsNil() if isNil { return errors.New("resources should not be nil") } if e, g := 1, len(resources); e != g { return errors.Errorf("len(resources): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Find resources by attribute type and name", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/find_resources_by_attribute_type_and_name.json", store); err != nil { return errors.WithStack(err) } resources, err := store.FindResources(ctx, share.WithType(share.TypeText), share.WithName("my_attr")) if err != nil { return errors.WithStack(err) } isNil := reflect.ValueOf(resources).IsNil() if isNil { return errors.New("resources should not be nil") } if e, g := 1, len(resources); e != g { return errors.Errorf("len(resources): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Get resource", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/get_resource.json", store); err != nil { return errors.WithStack(err) } origin := app.ID("app1.edge.app") resourceID := share.ResourceID("res-1") resource, err := store.GetResource(ctx, origin, resourceID) if err != nil { return errors.WithStack(err) } isNil := reflect.ValueOf(resource).IsNil() if isNil { return errors.New("resources should not be nil") } if e, g := origin, resource.Origin(); e != g { return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g) } if e, g := resourceID, resource.ID(); e != g { return errors.Errorf("resource.ID(): expected '%v', got '%v'", e, g) } resource, err = store.GetResource(ctx, origin, "unexistant-id") if err == nil { return errors.New("err should not be nil") } if !errors.Is(err, share.ErrNotFound) { return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err) } return nil }, }, { Name: "Delete resource", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/delete_resource.json", store); err != nil { return errors.WithStack(err) } origin := app.ID("app1.edge.app") resourceID := share.ResourceID("res-1") // It should delete an existing resource if err := store.DeleteResource(ctx, origin, resourceID); err != nil { return errors.WithStack(err) } _, err := store.GetResource(ctx, origin, resourceID) if err == nil { return errors.New("err should not be nil") } // The resource should be deleted if !errors.Is(err, share.ErrNotFound) { return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err) } // It should not delete an unexistant resource err = store.DeleteResource(ctx, origin, resourceID) if err == nil { return errors.New("err should not be nil") } if !errors.Is(err, share.ErrNotFound) { return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err) } otherOrigin := app.ID("app2.edge.app") // It should not delete a resource with the same id and another origin resource, err := store.GetResource(ctx, otherOrigin, resourceID) if err != nil { return errors.New("err should not be nil") } if e, g := otherOrigin, resource.Origin(); e != g { return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Delete attributes", Skip: false, Run: func(ctx context.Context, t *testing.T, store share.Store) error { if err := loadTestData(ctx, "testdata/delete_attributes.json", store); err != nil { return errors.WithStack(err) } origin := app.ID("app1.edge.app") resourceID := share.ResourceID("res-1") // It should delete specified attributes if err := store.DeleteAttributes(ctx, origin, resourceID, "my_text", "my_bool"); err != nil { return errors.WithStack(err) } resource, err := store.GetResource(ctx, origin, resourceID) if err != nil { return errors.WithStack(err) } if e, g := 1, len(resource.Attributes()); e != g { return errors.Errorf("len(resource.Attributes()): expected '%v', got '%v'", e, g) } attr := share.GetAttribute(resource, "my_number", share.TypeNumber) if attr == nil { return errors.New("attr shoudl not be nil") } return nil }, }, } func runRepositoryTests(t *testing.T, newRepo NewTestStoreFunc) { for _, tc := range repositoryTestCases { func(tc repositoryTestCase) { t.Run(tc.Name, func(t *testing.T) { t.Parallel() if tc.Skip { t.SkipNow() return } ctx := context.Background() repo, err := newRepo(tc.Name) if err != nil { t.Fatalf("%+v", errors.WithStack(err)) } if err := tc.Run(ctx, t, repo); err != nil { t.Errorf("%+v", errors.WithStack(err)) } }) }(tc) } } type jsonResource struct { ID string `json:"id"` Origin string `json:"origin"` Attributes []jsonAttribute `json:"attributes"` } type jsonAttribute struct { Name string `json:"name"` Type share.ValueType `json:"type"` Value any `json:"value"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func loadTestData(ctx context.Context, jsonFile string, store share.Store) error { data, err := testData.ReadFile(jsonFile) if err != nil { return errors.WithStack(err) } var resources []jsonResource if err := json.Unmarshal(data, &resources); err != nil { return errors.WithStack(err) } for _, res := range resources { attributes := make([]share.Attribute, len(res.Attributes)) for idx, attr := range res.Attributes { attributes[idx] = share.NewBaseAttribute( attr.Name, attr.Type, attr.Value, ) } _, err := store.UpdateAttributes(ctx, app.ID(res.Origin), share.ResourceID(res.ID), attributes...) if err != nil { return errors.WithStack(err) } } return nil }