diff --git a/compose/importer/chart_test.go b/compose/importer/chart_test.go index 0f425c068..b04712f01 100644 --- a/compose/importer/chart_test.go +++ b/compose/importer/chart_test.go @@ -1,82 +1,59 @@ package importer import ( - "fmt" - "os" + "errors" "testing" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" "github.com/cortezaproject/corteza-server/compose/types" - "github.com/cortezaproject/corteza-server/internal/permissions" ) -type ( - namespaceMock struct{ set types.NamespaceSet } - moduleMock struct{ set types.ModuleSet } - chartMock struct{ set types.ChartSet } - pageMock struct{ set types.PageSet } -) - -func (mock *namespaceMock) FindByHandle(handle string) (o *types.Namespace, err error) { - return -} - -func (mock *moduleMock) FindByHandle(namespaceID uint64, handle string) (o *types.Module, err error) { - return -} - -func (mock *chartMock) FindByHandle(namespaceID uint64, handle string) (o *types.Chart, err error) { - return -} - -func (mock *pageMock) FindByHandle(namespaceID uint64, handle string) (o *types.Page, err error) { - return -} - func TestChartImport_CastSet(t *testing.T) { - var ( - namespace = &namespaceMock{} - module = &moduleMock{} - chart = &chartMock{} - page = &pageMock{} - pi = permissions.NewImporter(nil) - - imp = NewImporter(namespace, module, chart, page, pi) - - ns = &types.Namespace{ - ID: 1000000, - Name: "Test", - Slug: "Test", - Enabled: true, - } - - impFixTester = func(t *testing.T, name string, fn func(*testing.T, *Chart)) { - t.Run(name, func(t *testing.T) { - var aux interface{} - req := require.New(t) - f, err := os.Open(fmt.Sprintf("testdata/%s.yaml", name)) - req.NoError(err) - req.NoError(yaml.NewDecoder(f).Decode(&aux)) - req.NotNil(aux) - ci := NewChartImporter(imp, ns) - req.NoError(ci.CastSet(aux)) - fn(t, ci) - }) - } - ) - - impFixTester(t, "chart_full_slice", func(t *testing.T, chart *Chart) { + impFixTester(t, "chart_full_slice", func(t *testing.T, imp *Importer) { req := require.New(t) - req.Len(chart.set, 2) + req.Len(imp.GetChartImporter("test").set, 2) }) + impFixTester(t, + "chart_with_unknown_module", + errors.New(`unknown module "un_kno_wn" referenced from chart "chart1" report config`)) + + // Pre fill with module that imported chart is referring to + // imp.namespaces.modules[ns.Slug] = &Module{set: []*types.Module{{Handle: "foo"}}} + modules.set = []*types.Module{{NamespaceID: ns.ID, Handle: "foo"}} + impFixTester(t, "chart_full", func(t *testing.T, chart *Chart) { req := require.New(t) + req.Len(chart.set, 2) - req.Equal(chart.set[0].Handle, "chart1") - req.Equal(chart.set[0].Name, "chart 1") + + req.NotNil(chart.set.FindByHandle("chart1")) + req.NotNil(chart.set.FindByHandle("chart2")) + + tc := chart.set.FindByHandle("chart1") + req.Equal(tc.Name, "chart 1") + req.Equal(tc.Config, types.ChartConfig{Reports: []*types.ChartConfigReport{ + { + Filter: "a=b", + ModuleID: 0, + Metrics: []map[string]interface{}{ + { + "backgroundColor": "#e5a83b", + "beginAtZero": true, + "field": "count", + "fixTooltips": true, + "label": "Number of leads", + "type": "bar", + }, + }, + Dimensions: []map[string]interface{}{ + { + "field": "created_at", + }, + }, + }, + }}) }) } diff --git a/compose/importer/importer.go b/compose/importer/importer.go index 444e9b864..ad51c5337 100644 --- a/compose/importer/importer.go +++ b/compose/importer/importer.go @@ -58,6 +58,18 @@ func (imp *Importer) GetNamespaceImporter() *Namespace { return imp.namespaces } +func (imp *Importer) GetModuleImporter(handle string) *Module { + return imp.namespaces.modules[handle] +} + +func (imp *Importer) GetPageImporter(handle string) *Page { + return imp.namespaces.pages[handle] +} + +func (imp *Importer) GetChartImporter(handle string) *Chart { + return imp.namespaces.charts[handle] +} + func (imp *Importer) Cast(in interface{}) (err error) { return deinterfacer.Each(in, func(index int, key string, val interface{}) (err error) { switch key { diff --git a/compose/importer/main_test.go b/compose/importer/main_test.go new file mode 100644 index 000000000..c96c3bea4 --- /dev/null +++ b/compose/importer/main_test.go @@ -0,0 +1,127 @@ +package importer + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" + + "github.com/cortezaproject/corteza-server/compose/repository" + "github.com/cortezaproject/corteza-server/compose/service" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/internal/permissions" +) + +type ( + namespaceMock struct{ set types.NamespaceSet } + moduleMock struct{ set types.ModuleSet } + chartMock struct{ set types.ChartSet } + pageMock struct{ set types.PageSet } +) + +var ( + ns = &types.Namespace{ + ID: 1000000, + Name: "Test", + Slug: "test", + Enabled: true, + } + + // Add namespace to the stack, make sure importer can find it + namespaces = &namespaceMock{set: types.NamespaceSet{ns}} + modules = &moduleMock{} + charts = &chartMock{} + pages = &pageMock{} + + // whitelist = nil, anything can be added + pi = permissions.NewImporter(service.AccessControl(nil).Whitelist()) + + imp = NewImporter(namespaces, modules, charts, pages, pi) +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func (mock *namespaceMock) FindByHandle(slug string) (o *types.Namespace, err error) { + oo, err := mock.set.Filter(func(o *types.Namespace) (b bool, e error) { + return o.Slug == slug, nil + }) + + if len(oo) > 0 { + return oo[0], nil + } else { + return nil, repository.ErrNamespaceNotFound + } +} + +func (mock *moduleMock) FindByHandle(namespaceID uint64, handle string) (o *types.Module, err error) { + oo, err := mock.set.Filter(func(o *types.Module) (b bool, e error) { + return o.Handle == handle && o.NamespaceID == namespaceID, nil + }) + + if len(oo) > 0 { + return oo[0], nil + } else { + return nil, repository.ErrModuleNotFound + } +} + +func (mock *chartMock) FindByHandle(namespaceID uint64, handle string) (o *types.Chart, err error) { + oo, err := mock.set.Filter(func(o *types.Chart) (b bool, e error) { + return o.Handle == handle && o.NamespaceID == namespaceID, nil + }) + + if len(oo) > 0 { + return oo[0], nil + } else { + return nil, repository.ErrChartNotFound + } +} + +func (mock *pageMock) FindByHandle(namespaceID uint64, handle string) (o *types.Page, err error) { + oo, err := mock.set.Filter(func(o *types.Page) (b bool, e error) { + return o.Handle == handle && o.NamespaceID == namespaceID, nil + }) + + if len(oo) > 0 { + return oo[0], nil + } else { + return nil, repository.ErrPageNotFound + } +} + +func impFixTester(t *testing.T, name string, tester interface{}) { + t.Run(name, func(t *testing.T) { + var aux interface{} + req := require.New(t) + f, err := os.Open(fmt.Sprintf("testdata/%s.yaml", name)) + req.NoError(err) + req.NoError(yaml.NewDecoder(f).Decode(&aux)) + req.NotNil(aux) + + if reqError, ok := tester.(error); ok { + req.EqualError(imp.GetNamespaceImporter().Cast(ns.Slug, aux), reqError.Error()) + return + } else { + req.NoError(imp.GetNamespaceImporter().Cast(ns.Slug, aux)) + } + + switch tester := tester.(type) { + case func(*testing.T, *Namespace): + tester(t, imp.GetNamespaceImporter()) + case func(*testing.T, *Module): + tester(t, imp.GetModuleImporter(ns.Slug)) + case func(*testing.T, *Chart): + tester(t, imp.GetChartImporter(ns.Slug)) + case func(*testing.T, *Page): + tester(t, imp.GetPageImporter(ns.Slug)) + case func(*testing.T, *Importer): + tester(t, imp) + default: + panic("unsupported tester function signature") + } + }) +} diff --git a/compose/importer/module.go b/compose/importer/module.go index b0d528291..a180c33e6 100644 --- a/compose/importer/module.go +++ b/compose/importer/module.go @@ -204,6 +204,7 @@ func (mImp *Module) castFields(module *types.Module, def interface{}) (err error } func (mImp *Module) castFieldOptions(field *types.ModuleField, def interface{}) (err error) { + field.Options = map[string]interface{}{} return deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) { field.Options[key] = deinterfacer.Simplify(val) return diff --git a/compose/importer/module_test.go b/compose/importer/module_test.go index 1e0879a5e..df540d924 100644 --- a/compose/importer/module_test.go +++ b/compose/importer/module_test.go @@ -2,9 +2,39 @@ package importer import ( "testing" + + "github.com/stretchr/testify/require" + + "github.com/cortezaproject/corteza-server/compose/types" ) func TestModuleImport_CastSet(t *testing.T) { - t.Skip() + impFixTester(t, "module_full", func(t *testing.T, module *Module) { + req := require.New(t) + req.Len(module.set, 1) + + tc := module.set.FindByHandle("mod1") + req.Equal(tc.Name, "Module with fields") + req.Equal(tc.Fields, types.ModuleFieldSet{ + { + Name: "f1", + Kind: "Number", + Label: "f1", + }, + { + + Name: "f2", + Kind: "String", + Label: "F2", + Place: 1, + Required: true, + Options: map[string]interface{}{ + "multiLine": true, + "useRichTextEditor": true, + "multiDelimiter": "\n", + }, + }, + }) + }) } diff --git a/compose/importer/page.go b/compose/importer/page.go index eeae6a15d..d4d296dd1 100644 --- a/compose/importer/page.go +++ b/compose/importer/page.go @@ -165,9 +165,12 @@ func (pImp *Page) castBlocks(page *types.Page, def interface{}) error { block.Kind = deinterfacer.ToString(val) case "options": - if block.Options, err = pImp.castBlockOptions(val); err != nil { - return err - } + block.Options, err = pImp.castBlockOptions(val) + return err + + case "style": + block.Style, err = pImp.castBlockStyle(page, b, val) + return case "XYWH", "xywh", "dim", "dimension": xywh := deinterfacer.ToInts(val) @@ -203,6 +206,24 @@ func (pImp *Page) castBlockOptions(def interface{}) (opt map[string]interface{}, }) } +func (pImp *Page) castBlockStyle(page *types.Page, n int, def interface{}) (s types.PageBlockStyle, err error) { + s = types.PageBlockStyle{} + + return s, deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) { + switch key { + case "variants": + s.Variants = map[string]string{} + return deinterfacer.Each(val, func(_ int, key string, val interface{}) (err error) { + s.Variants[key] = deinterfacer.ToString(val) + return + }) + default: + return fmt.Errorf("unexpected key %q for block #%d on page %q", key, n+1, page.Handle) + + } + }) +} + func (pImp *Page) Exists(handle string) bool { handle = importer.NormalizeHandle(handle) diff --git a/compose/importer/page_test.go b/compose/importer/page_test.go index e08d8d462..6104df888 100644 --- a/compose/importer/page_test.go +++ b/compose/importer/page_test.go @@ -2,8 +2,57 @@ package importer import ( "testing" + + "github.com/stretchr/testify/require" + + "github.com/cortezaproject/corteza-server/compose/types" ) func TestPageImport_CastSet(t *testing.T) { - t.Skip() + impFixTester(t, "page_full", func(t *testing.T, page *Page) { + req := require.New(t) + + req.Len(page.set, 4) + + root := page.set.FindByHandle("root") + req.NotNil(root) + req.Equal(root.Title, "Root page") + + sub1 := page.set.FindByHandle("sub1") + req.NotNil(sub1) + req.Equal(sub1.Title, "Sub page 1") + req.Equal(sub1.Blocks, types.PageBlocks{ + { + Title: "B1", + Options: nil, + Style: types.PageBlockStyle{ + Variants: map[string]string{"v1": "V1"}, + }, + Kind: "TheTestingKind", + XYWH: [4]int{1, 2, 3, 4}, + }, + { + Title: "B2", + Options: nil, + Style: types.PageBlockStyle{}, + Kind: "TheTestingKind", + XYWH: [4]int{11, 12, 13, 14}, + }, + }) + + sub2 := page.set.FindByHandle("sub2") + req.NotNil(sub2) + req.Equal(sub2.Title, "Sub page 2") + + sub21 := page.set.FindByHandle("sub21") + req.NotNil(sub21) + req.Equal(sub21.Title, "Sub-sub page 2.1") + + req.Equal(page.tree, map[string][]string{ + "": {"root"}, + "root": {"sub1", "sub2"}, + "sub2": {"sub21"}, + }) + + }) } diff --git a/compose/importer/testdata/chart_full.yaml b/compose/importer/testdata/chart_full.yaml index 7475c8f14..dd996db34 100644 --- a/compose/importer/testdata/chart_full.yaml +++ b/compose/importer/testdata/chart_full.yaml @@ -1,4 +1,18 @@ -chart1: - name: chart 1 -chart2: - name: chart 2 +charts: + chart1: + name: chart 1 + config: + reports: + - filter: a=b + module: foo + metrics: + - backgroundColor: '#e5a83b' + beginAtZero: true + field: count + fixTooltips: true + label: Number of leads + type: bar + dimensions: + - field: created_at + chart2: + name: chart 2 diff --git a/compose/importer/testdata/chart_full_slice.yaml b/compose/importer/testdata/chart_full_slice.yaml index bdbdf706b..ad8edf2fe 100644 --- a/compose/importer/testdata/chart_full_slice.yaml +++ b/compose/importer/testdata/chart_full_slice.yaml @@ -1,4 +1,5 @@ -- handle: chart1 - name: chart 1 -- handle: chart2 - name: chart 2 +charts: + - handle: chart1 + name: chart 1 + - handle: chart2 + name: chart 2 diff --git a/compose/importer/testdata/chart_with_unknown_module.yaml b/compose/importer/testdata/chart_with_unknown_module.yaml new file mode 100644 index 000000000..06bdb8104 --- /dev/null +++ b/compose/importer/testdata/chart_with_unknown_module.yaml @@ -0,0 +1,6 @@ +charts: + chart1: + name: chart 1 + config: + reports: + - module: un_kno_wn diff --git a/compose/importer/testdata/module_full.yaml b/compose/importer/testdata/module_full.yaml new file mode 100644 index 000000000..562b85ef6 --- /dev/null +++ b/compose/importer/testdata/module_full.yaml @@ -0,0 +1,14 @@ +modules: + mod1: + name: Module with fields + fields: + f1: Number + f2: + kind: String + label: F2 + required: true + options: + multiLine: true + useRichTextEditor: true + multiDelimiter: |2+ + diff --git a/compose/importer/testdata/page_full.yaml b/compose/importer/testdata/page_full.yaml new file mode 100644 index 000000000..8bd22269c --- /dev/null +++ b/compose/importer/testdata/page_full.yaml @@ -0,0 +1,19 @@ +pages: + root: + title: Root page + pages: + sub1: + title: Sub page 1 + blocks: + - title: B1 + xywh: [ 1, 2, 3, 4 ] + kind: TheTestingKind + style: { variants: { v1: "V1" } } + - title: B2 + xywh: [ 11, 12, 13, 14 ] + kind: TheTestingKind + sub2: + title: Sub page 2 + pages: + sub21: + title: Sub-sub page 2.1