diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..a5648e2 Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4c5cd1c..e424895 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,6 +6,7 @@ on: pull_request: branches: - main + jobs: test: name: Test @@ -45,3 +46,33 @@ jobs: - name: Test example/devdata_cli (sub module) run: cd ./examples/devdata_cli && go test -race ./... + + integration_test: + name: Integration test + strategy: + matrix: + # boolean values are cast to indices + environments: ${{ fromJSON('[["staging"], ["staging", "production"]]')[github.ref == 'refs/heads/main'] }} + runs-on: ubuntu-latest + environment: ${{ matrix.environments }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Required for go-header check. + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + cache-dependency-path: | + go.sum + examples/devdata_cli/go.sum + id: go + + - name: Test (main module) + run: go test -race ./test/... + env: + CLARIFY_USERNAME: ${{ secrets.CLARIFY_USERNAME }} + CLARIFY_PASSWORD: ${{ secrets.CLARIFY_PASSWORD }} + CLARIFY_ENDPOINT: ${{ secrets.CLARIFY_ENDPOINT }} diff --git a/README.md b/README.md index 0f7c942..a8bb29d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ By using our [automation](automation) and [automationcli](automation/automationc ## Copyright -Copyright 2022-2023 Searis AS +Copyright 2022-2024 Searis AS Licensed under the Apache License, Version 2.0 (the "License"); you may not use the content in this repo except in compliance with the License. diff --git a/client.go b/client.go index 75b6759..e2f425b 100644 --- a/client.go +++ b/client.go @@ -298,14 +298,20 @@ func (er EvaluateRequest) Include(relationships ...string) EvaluateRequest { return er } +func (er EvaluateRequest) Format(format views.SelectionFormat) EvaluateRequest { + er.format = format + + return er +} + func (er EvaluateRequest) Do(ctx context.Context) (*EvaluateResult, error) { r := methodEvaluate.NewRequest(er.h, paramData.Value(er.data), paramItems.Value(er.items), paramGroups.Value(er.groups), - paramCalculations.Value(er.calculations)) - - r.Include(er.relationships...) + paramCalculations.Value(er.calculations), + paramFormat.Value(er.format)). + Include(er.relationships...) return r.Do(ctx) } @@ -318,6 +324,7 @@ type EvaluateRequest struct { groups []fields.EvaluateGroup calculations []fields.Calculation relationships []string + format views.SelectionFormat h jsonrpc.Handler } diff --git a/fields/evaluate.go b/fields/evaluate.go index 952cc8e..abe414c 100644 --- a/fields/evaluate.go +++ b/fields/evaluate.go @@ -193,17 +193,24 @@ func (ei EvaluateItem) MarshalJSON() ([]byte, error) { func (eg EvaluateGroup) MarshalJSON() ([]byte, error) { var v any - type encType struct { - Alias string `json:"alias,omitempty"` - Query ResourceQuery `json:"query,omitempty"` - TimeAggregation TimeAggregation `json:"timeAggregation,omitempty"` - GroupAggregation GroupAggregation `json:"groupAggregation,omitempty"` - State int `json:"-"` - Lead int `json:"lead,omitempty"` - Lag int `json:"lag,omitempty"` - } + switch eg.TimeAggregation { + case TimeAggregationSeconds, TimeAggregationPercent, TimeAggregationRate: + type encType EvaluateGroup - v = encType(eg) + v = encType(eg) + default: + type encType struct { + Alias string `json:"alias,omitempty"` + Query ResourceQuery `json:"query,omitempty"` + TimeAggregation TimeAggregation `json:"timeAggregation,omitempty"` + GroupAggregation GroupAggregation `json:"groupAggregation,omitempty"` + State int `json:"-"` + Lead int `json:"lead,omitempty"` + Lag int `json:"lag,omitempty"` + } + + v = encType(eg) + } return json.Marshal(v) } diff --git a/test/common.go b/test/common.go new file mode 100644 index 0000000..3850294 --- /dev/null +++ b/test/common.go @@ -0,0 +1,146 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "encoding/json" + "os" + "runtime" + "strings" + "testing" + "time" + + "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" +) + +const ( + AnnotationKey = "there-is-always-money" + AnnotationValue = "in-the-banana-stand" +) + +func getDefaultTimeRange() (time.Time, time.Time) { + tidenesMorgen := time.Unix(0, 0).UTC().Truncate(time.Hour) + tidenesKveld := tidenesMorgen.Add(10 * time.Hour) + + return tidenesMorgen, tidenesKveld +} + +func testName() string { + pc := make([]uintptr, 10) + n := runtime.Callers(3, pc) + frame, _ := runtime.CallersFrames(pc[:n]).Next() + funks := strings.Split(frame.Function, ".") + funk := funks[len(funks)-1] + + return funk +} + +func createPrefix() string { + test := testName() + + return test + "/" +} + +func getCredentials(t *testing.T) *clarify.Credentials { + var creds *clarify.Credentials + + username := os.Getenv("CLARIFY_USERNAME") + password := os.Getenv("CLARIFY_PASSWORD") + endpoint := os.Getenv("CLARIFY_ENDPOINT") + credentials := os.Getenv("CLARIFY_CREDENTIALS") + + switch { + case username != "" && password != "": + creds = clarify.BasicAuthCredentials(username, password) + + if endpoint != "" { + creds.APIURL = endpoint + } + case credentials != "": + var err error + creds, err = clarify.CredentialsFromFile(credentials) + if err != nil { + t.Fatalf("failed to parse credentials file: %s", err) + } + default: + t.Skip("no credentials found, skipping integration tests") + } + + if err := creds.Validate(); err != nil { + t.Fatalf("invalid credentials: %s", err) + } + + return creds +} + +func jsonEncode[v any](t *testing.T, a v) { + enc := json.NewEncoder(os.Stdout) + + enc.SetIndent("", " ") + + err := enc.Encode(a) + if err != nil { + t.Errorf("%v", err) + } +} + +func createAnnotationQuery(prefix string) fields.ResourceQuery { + return fields.Query(). + Where(fields.Comparisons{"annotations." + prefix + AnnotationKey: fields.Equal(AnnotationValue)}). + Limit(10) +} + +func onlyError[R any](f func(TestArgs) (R, error)) func(TestArgs) error { + return func(a TestArgs) error { + _, err := f(a) + + return err + } +} + +func applyTestArgs(a TestArgs, fs ...func(a TestArgs) error) { + for _, f := range fs { + if err := f(a); err != nil { + panic(err) + } + } +} + +type TestArgs struct { + ctx context.Context + integration string + client *clarify.Client + prefix string +} + +func Map[A any, B any](f func(a A) B, as []A) []B { + g := func(index int, a A) B { + return f(a) + } + + return MapIndex(g, as) +} + +func MapIndex[A any, B any](f func(i int, a A) B, as []A) []B { + bs := make([]B, len(as)) + + for i := range as { + bs[i] = f(i, as[i]) + } + + return bs +} diff --git a/test/data_frame_integration_test.go b/test/data_frame_integration_test.go new file mode 100644 index 0000000..833759c --- /dev/null +++ b/test/data_frame_integration_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "testing" + "time" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" +) + +func TestDataFrame(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault), onlyError(saveSignalsDefault), onlyError(publishSignalsDefault)) + + t0, t1 := getDefaultTimeRange() + + type testCase struct { + testArgs TestArgs + items fields.ResourceQuery + data fields.DataQuery + expectedFields func(*clarify.DataFrameResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := dataFrame(tc.testArgs.ctx, tc.testArgs.client, tc.items, tc.data) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + t.Run("basic data frame test", test(testCase{ + testArgs: a, + items: createAnnotationQuery(a.prefix), + data: fields.Data().Where(fields.TimeRange(t0, t1)).RollupDuration(time.Hour, time.Monday), + expectedFields: func(dfr *clarify.DataFrameResult) bool { + return true + }, + })) + + t.Run("less basic data frame test", test(testCase{ + testArgs: a, + items: createAnnotationQuery(a.prefix), + data: fields.Data().Where(fields.TimeRange(t1, t0)).RollupDuration(time.Hour, time.Monday), + expectedFields: func(dfr *clarify.DataFrameResult) bool { + return true + }, + })) +} + +func dataFrame(ctx context.Context, client *clarify.Client, items fields.ResourceQuery, data fields.DataQuery) (*clarify.DataFrameResult, error) { + result, err := client.Clarify().DataFrame(items, data).Do(ctx) + + return result, err +} + +//lint:ignore U1000 Ignore unused function temporarily for debugging +func dataFrameDefault(a TestArgs) (*clarify.DataFrameResult, error) { + items := createAnnotationQuery(a.prefix) + t0, t1 := getDefaultTimeRange() + data := fields.Data(). + Where(fields.TimeRange(t0, t1)). + RollupDuration(time.Hour, time.Monday) + + return dataFrame(a.ctx, a.client, items, data) +} diff --git a/test/evaluate_integration_test.go b/test/evaluate_integration_test.go new file mode 100644 index 0000000..2f82ced --- /dev/null +++ b/test/evaluate_integration_test.go @@ -0,0 +1,186 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "fmt" + "testing" + "time" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" + "github.com/clarify/clarify-go/views" +) + +func TestEvaluate(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault), onlyError(saveSignalsDefault), onlyError(publishSignalsDefault)) + + t0, t1 := getDefaultTimeRange() + ir, err := selectItemsDefault(a) + if err != nil { + t.Errorf("%v", err) + } + + itemIDs := getListFromResponse(ir) + + type testCase struct { + testArgs TestArgs + itemIDs []string + query fields.ResourceQuery + data fields.DataQuery + timeAggregation fields.TimeAggregation + groupAggregation fields.GroupAggregation + expectedFields func(*clarify.EvaluateResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := evaluate(tc.testArgs.ctx, tc.testArgs.client, tc.itemIDs, tc.query, tc.data, tc.timeAggregation, tc.groupAggregation) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + ef := func(er *clarify.EvaluateResult) bool { + // this could be done better! + // like some kind of order of items, groups, and calculations as test input, + // looped through here to verify they exist in the output. + keys := []string{"i0", "i1", "c1", "c2", "c3", "g1"} + ok := true + for _, key := range keys { + _, kok := er.Data[key] + ok = ok && kok + } + + return ok + } + + t.Run("basic evaluate test", test(testCase{ + testArgs: a, + itemIDs: itemIDs, + query: createAnnotationQuery(a.prefix), + data: fields.Data().Where(fields.TimeRange(t0, t1)).RollupDuration(time.Hour, time.Monday), + timeAggregation: fields.TimeAggregationAvg, + groupAggregation: fields.GroupAggregationAvg, + expectedFields: ef, + })) + + taggs := []fields.TimeAggregation{ + fields.TimeAggregationCount, + fields.TimeAggregationMin, + fields.TimeAggregationMax, + fields.TimeAggregationSum, + fields.TimeAggregationAvg, + fields.TimeAggregationSeconds, + fields.TimeAggregationPercent, + fields.TimeAggregationRate, + } + for _, taggs := range taggs { + t.Run("time aggregation test type "+fmt.Sprint(taggs), test(testCase{ + testArgs: a, + itemIDs: itemIDs, + query: createAnnotationQuery(a.prefix), + data: fields.Data().Where(fields.TimeRange(t0, t1)).RollupDuration(time.Hour, time.Monday), + timeAggregation: taggs, + groupAggregation: fields.GroupAggregationAvg, + expectedFields: ef, + })) + } + + gaggs := []fields.GroupAggregation{ + fields.GroupAggregationCount, + fields.GroupAggregationMin, + fields.GroupAggregationMax, + fields.GroupAggregationSum, + fields.GroupAggregationAvg, + } + for _, gagg := range gaggs { + t.Run("group aggregation test type "+fmt.Sprint(gagg), test(testCase{ + testArgs: a, + itemIDs: itemIDs, + query: createAnnotationQuery(a.prefix), + data: fields.Data().Where(fields.TimeRange(t0, t1)).RollupDuration(time.Hour, time.Monday), + timeAggregation: fields.TimeAggregationAvg, + groupAggregation: gagg, + expectedFields: ef, + })) + } +} + +func evaluate(ctx context.Context, client *clarify.Client, itemIDs []string, query fields.ResourceQuery, data fields.DataQuery, timeAggregation fields.TimeAggregation, groupAggregation fields.GroupAggregation) (*clarify.EvaluateResult, error) { + f := func(i int, itemID string) fields.EvaluateItem { + return fields.EvaluateItem{ + Alias: fmt.Sprintf("i%d", i), + ID: itemID, + TimeAggregation: timeAggregation, + State: 10, + } + } + items := MapIndex(f, itemIDs) + groups := []fields.EvaluateGroup{ + { + Alias: "g1", + Query: query, + TimeAggregation: timeAggregation, + GroupAggregation: groupAggregation, + State: 10, + }, + } + calculations := []fields.Calculation{ + {Alias: "c1", Formula: "sin(g1)"}, + {Alias: "c2", Formula: "sin(2*PI*time_seconds/3600)"}, + {Alias: "c3", Formula: "max(c1,c2)"}, + } + format := views.SelectionFormat{ + GroupIncludedByType: true, + } + result, err := client.Clarify().Evaluate(data). + Items(items...). + Groups(groups...). + Calculations(calculations...). + Include([]string{"items"}...). + Format(format). + Do(ctx) + + return result, err +} + +func getListFromResponse(sir *clarify.SelectItemsResult) []string { + itemIDs := make([]string, 0, len(sir.Data)) + for _, d := range sir.Data { + itemIDs = append(itemIDs, d.ID) + } + + return itemIDs +} diff --git a/test/insert_integration_test.go b/test/insert_integration_test.go new file mode 100644 index 0000000..f37b8b4 --- /dev/null +++ b/test/insert_integration_test.go @@ -0,0 +1,95 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "math/rand" + "testing" + "time" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" + "github.com/clarify/clarify-go/views" +) + +func TestInsert(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + type testCase struct { + testArgs TestArgs + expectedFields func(*clarify.InsertResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := insert(tc.testArgs.ctx, tc.testArgs.client, tc.testArgs.prefix) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + t.Run("basic insert test", test(testCase{ + testArgs: a, + expectedFields: func(ir *clarify.InsertResult) bool { + return true + }, + })) +} + +func insert(ctx context.Context, client *clarify.Client, prefix string) (*clarify.InsertResult, error) { + tt0, tt1 := getDefaultTimeRange() + t0 := fields.AsTimestamp(tt0) + segmentSize := 15 * time.Minute + segments := int(tt1.Sub(tt0)) / int(segmentSize) + + amount := make(views.DataSeries) + for i := 0; i < segments; i++ { + amount[t0.Add(time.Duration(i*int(segmentSize)))] = rand.Float64() * float64(i) + } + + status := make(views.DataSeries) + for i := 0; i < segments; i++ { + status[t0.Add(time.Duration(i*int(segmentSize)))] = float64(rand.Intn(5)) + } + + df := views.DataFrame{ + prefix + "banana-stand/amount": amount, + prefix + "banana-stand/status": status, + } + result, err := client.Insert(df).Do(ctx) + + return result, err +} + +func insertDefault(a TestArgs) (*clarify.InsertResult, error) { + return insert(a.ctx, a.client, a.prefix) +} diff --git a/test/publish_signals_integration_test.go b/test/publish_signals_integration_test.go new file mode 100644 index 0000000..922a7e9 --- /dev/null +++ b/test/publish_signals_integration_test.go @@ -0,0 +1,137 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "math/rand" + "testing" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/automation" + "github.com/clarify/clarify-go/fields" + "github.com/clarify/clarify-go/views" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +func TestPublishSignals(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault), onlyError(saveSignalsDefault)) + + type testCase struct { + testArgs TestArgs + config *automation.Config + expectedFields func(*clarify.PublishSignalsResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := publishSignals(tc.testArgs.ctx, tc.testArgs.integration, tc.testArgs.prefix, tc.config) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + // FIXME: before enabling `jsonEncode(t, result)` i need to move away from using automation to using the publish signals function directly. + } + } + + t.Run("basic publish signals test", test(testCase{ + testArgs: a, + config: defaultCfg(a.client), + expectedFields: func(*clarify.PublishSignalsResult) bool { + return true + }, + })) +} + +func publishSignals(ctx context.Context, integration string, prefix string, cfg *automation.Config) (*clarify.PublishSignalsResult, error) { + keyExamplePublish := "clarify/clarify-go/example/publish" + annotationTrue := "true" + transformVersion := "v2" + routine := automation.PublishSignals{ + Integrations: []string{integration}, + SignalsFilter: fields.Comparisons{ + "annotations." + prefix + AnnotationKey: fields.Equal(AnnotationValue), + "annotations." + prefix + keyExamplePublish: fields.Equal(annotationTrue), + }, + TransformVersion: transformVersion, + Transforms: []func(item *views.ItemSave){ + transformEnumValuesToEmoji, + transformLabelValuesToTitle, + createTransform(prefix), + }, + } + err := routine.Do(ctx, cfg) + + return nil, err +} + +func transformEnumValuesToEmoji(item *views.ItemSave) { + for i := range item.EnumValues { + item.EnumValues[i] = getRandom(emojiList) + } +} + +// transformLabelValuesToTitle transforms label values from format "multiple +// words" to "Multiple Words". +func transformLabelValuesToTitle(item *views.ItemSave) { + for k, labels := range item.Labels { + for i, label := range labels { + item.Labels[k][i] = cases.Title(language.AmericanEnglish).String(label) + } + } +} + +func createTransform(prefix string) func(item *views.ItemSave) { + return func(item *views.ItemSave) { + item.Annotations.Set(prefix+AnnotationKey, AnnotationValue) + } +} + +func getRandom[T any](as []T) T { + return as[rand.Intn(len(as))] +} + +var emojiList = []string{ + "🔴", "🟠", "🟡", "🟢", "🔵", "🟣", +} + +func defaultCfg(client *clarify.Client) *automation.Config { + cfg := automation.NewConfig(client) + cfg = cfg.WithEarlyOut(true) + cfg = cfg.WithAppName("rotrotrot") + + return cfg +} + +func publishSignalsDefault(a TestArgs) (*clarify.PublishSignalsResult, error) { + return publishSignals(a.ctx, a.integration, a.prefix, defaultCfg(a.client)) +} diff --git a/test/save_signals_integration_test.go b/test/save_signals_integration_test.go new file mode 100644 index 0000000..a6f4ab9 --- /dev/null +++ b/test/save_signals_integration_test.go @@ -0,0 +1,129 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "fmt" + "testing" + "time" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" + "github.com/clarify/clarify-go/views" +) + +func TestSaveSignals(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault)) + + type testCase struct { + testArgs TestArgs + expectedFields func(*clarify.SaveSignalsResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := saveSignals(tc.testArgs.ctx, tc.testArgs.client, tc.testArgs.prefix) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + t.Run("basic save signals test", test(testCase{ + testArgs: a, + expectedFields: func(ssr *clarify.SaveSignalsResult) bool { + return true + }, + })) +} + +func saveSignals(ctx context.Context, client *clarify.Client, prefix string) (*clarify.SaveSignalsResult, error) { + inputs := map[string]views.SignalSave{ + prefix + "banana-stand/amount": { + MetaSave: views.MetaSave{ + Annotations: fields.Annotations{ + prefix + AnnotationKey: AnnotationValue, + prefix + "clarify/clarify-go/example/publish": "true", + }, + }, + SignalSaveAttributes: views.SignalSaveAttributes{ + Name: prefix + "Amount", + Description: "Amount at location, counted manually.", + Labels: fields.Labels{ + "data-source": {"manual"}, + "location": {"banana stand", "pier"}, + }, + EngUnit: "USD", + }, + }, + prefix + "banana-stand/status": { + MetaSave: views.MetaSave{ + Annotations: fields.Annotations{ + prefix + AnnotationKey: AnnotationValue, + prefix + "clarify/clarify-go/example/publish": "true", + }, + }, + SignalSaveAttributes: views.SignalSaveAttributes{ + Name: prefix + "Building status", + Description: "Overall building status, aggregated from environmental sensors.", + Labels: fields.Labels{ + "data-source": {"environmental sensors"}, + "location": {"banana stand", "pier"}, + }, + SourceType: views.Aggregation, + ValueType: views.Enum, + EnumValues: createEnumValues(), + SampleInterval: fields.AsFixedDurationNullZero(15 * time.Minute), + GapDetection: fields.AsFixedDurationNullZero(2 * time.Hour), + }, + }, + } + result, err := client.SaveSignals(inputs).Do(ctx) + + return result, err +} + +func createEnumValues() fields.EnumValues { + ev := make(fields.EnumValues) + + for i := 0; i < 10; i++ { + ev[i] = fmt.Sprintf("%d", i) + } + + return ev +} + +func saveSignalsDefault(a TestArgs) (*clarify.SaveSignalsResult, error) { + return saveSignals(a.ctx, a.client, a.prefix) +} diff --git a/test/select_items_integration_test.go b/test/select_items_integration_test.go new file mode 100644 index 0000000..a4abea0 --- /dev/null +++ b/test/select_items_integration_test.go @@ -0,0 +1,80 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "testing" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" +) + +func TestSelectItems(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault), onlyError(saveSignalsDefault), onlyError(publishSignalsDefault)) + + type testCase struct { + testArgs TestArgs + items fields.ResourceQuery + expectedFields func(*clarify.SelectItemsResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := selectItems(tc.testArgs.ctx, tc.testArgs.client, tc.items) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + t.Run("basic select items test", test(testCase{ + testArgs: a, + items: createAnnotationQuery(a.prefix), + expectedFields: func(sir *clarify.SelectItemsResult) bool { + return true + }, + })) +} + +func selectItems(ctx context.Context, client *clarify.Client, items fields.ResourceQuery) (*clarify.SelectItemsResult, error) { + result, err := client.Clarify().SelectItems(items).Do(ctx) + + return result, err +} + +func selectItemsDefault(a TestArgs) (*clarify.SelectItemsResult, error) { + items := createAnnotationQuery(a.prefix) + + return selectItems(a.ctx, a.client, items) +} diff --git a/test/select_signals_integration_test.go b/test/select_signals_integration_test.go new file mode 100644 index 0000000..5a5f717 --- /dev/null +++ b/test/select_signals_integration_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 Searis AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "context" + "testing" + + clarify "github.com/clarify/clarify-go" + "github.com/clarify/clarify-go/fields" +) + +func TestSelectSignal(t *testing.T) { + ctx := context.Background() + creds := getCredentials(t) + client := creds.Client(ctx) + prefix := createPrefix() + a := TestArgs{ + ctx: ctx, + integration: creds.Integration, + client: client, + prefix: prefix, + } + + applyTestArgs(a, onlyError(insertDefault), onlyError(saveSignalsDefault)) + + type testCase struct { + testArgs TestArgs + signals fields.ResourceQuery + expectedFields func(*clarify.SelectSignalsResult) bool + } + + test := func(tc testCase) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + + result, err := selectSignals(tc.testArgs.ctx, tc.testArgs.client, tc.testArgs.integration, tc.signals) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } else if !tc.expectedFields(result) { + t.Errorf("unexpected field found!") + } + + jsonEncode(t, result) + } + } + + t.Run("basic select signals test", test(testCase{ + testArgs: a, + signals: createAnnotationQuery(a.prefix), + expectedFields: func(ssr *clarify.SelectSignalsResult) bool { + return true + }, + })) +} + +func selectSignals(ctx context.Context, client *clarify.Client, integration string, signals fields.ResourceQuery) (*clarify.SelectSignalsResult, error) { + result, err := client.Admin().SelectSignals(integration, signals).Do(ctx) + + return result, err +}