From 2a554445930b73cba3829fabb7dc2a0cd7ff17a6 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Fri, 19 Jan 2024 16:32:29 +0100 Subject: [PATCH] Refactor and add unit tests (#422) --- bridge/net/config.go | 63 ++++++++ bridge/net/config_test.go | 103 ++++++++++++ bridge/net/network.go | 98 ------------ bridge/net/request.go | 95 +++++++++++ bridge/net/request_test.go | 148 ++++++++++++++++++ bridge/resources/cbor.go | 47 ++++++ ...resource_internal_test.go => cbor_test.go} | 23 ++- bridge/resources/device/resource.go | 28 +--- bridge/resources/etag.go | 18 +++ bridge/resources/etag_test.go | 60 +++++++ bridge/resources/resource.go | 9 -- bridge/resources/uuid.go | 29 ++++ bridge/resources/uuid_test.go | 59 +++++++ 13 files changed, 644 insertions(+), 136 deletions(-) create mode 100644 bridge/net/config.go create mode 100644 bridge/net/config_test.go create mode 100644 bridge/net/request.go create mode 100644 bridge/net/request_test.go create mode 100644 bridge/resources/cbor.go rename bridge/resources/{device/resource_internal_test.go => cbor_test.go} (66%) create mode 100644 bridge/resources/etag_test.go create mode 100644 bridge/resources/uuid.go create mode 100644 bridge/resources/uuid_test.go diff --git a/bridge/net/config.go b/bridge/net/config.go new file mode 100644 index 00000000..dfd867b6 --- /dev/null +++ b/bridge/net/config.go @@ -0,0 +1,63 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgn.dev s.r.o. + * + * 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 implien. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +package net + +import ( + "fmt" + gonet "net" + "strconv" +) + +type Config struct { + ExternalAddress string `yaml:"externalAddress"` + MaxMessageSize uint32 `yaml:"maxMessageSize"` + externalAddressPort string `yaml:"-"` +} + +const DefaultMaxMessageSize = 2 * 1024 * 1024 + +func (cfg *Config) ExternalAddressPort() string { + return cfg.externalAddressPort +} + +func (cfg *Config) Validate() error { + if cfg.ExternalAddress == "" { + return fmt.Errorf("externalAddress is required") + } + host, portStr, err := gonet.SplitHostPort(cfg.ExternalAddress) + if err != nil { + return fmt.Errorf("invalid externalAddress: %w", err) + } + if host == "" { + return fmt.Errorf("invalid externalAddress: host cannot be empty") + } + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return fmt.Errorf("invalid externalAddress: %w", err) + } + if port == 0 { + return fmt.Errorf("invalid externalAddress: port cannot be 0") + } + if cfg.MaxMessageSize == 0 { + cfg.MaxMessageSize = DefaultMaxMessageSize + } + + cfg.externalAddressPort = portStr + return nil +} diff --git a/bridge/net/config_test.go b/bridge/net/config_test.go new file mode 100644 index 00000000..736f37f2 --- /dev/null +++ b/bridge/net/config_test.go @@ -0,0 +1,103 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgn.dev s.r.o. + * + * 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 implien. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +package net_test + +import ( + "testing" + + "github.com/plgd-dev/device/v2/bridge/net" + "github.com/stretchr/testify/require" +) + +func TestConfigValidate(t *testing.T) { + type data struct { + maxMsgSize uint32 + externalAddress string + externalAddressPort string + } + tests := []struct { + name string + config *net.Config + wantErr bool + want data + }{ + { + name: "ValidConfig", + config: &net.Config{ExternalAddress: "localhost:12345", MaxMessageSize: 1024}, + want: data{ + maxMsgSize: 1024, + externalAddress: "localhost:12345", + externalAddressPort: "12345", + }, + }, + { + name: "ValidConfigWithDefaultMaxMessageSize", + config: &net.Config{ExternalAddress: "localhost:12345"}, + want: data{ + maxMsgSize: net.DefaultMaxMessageSize, + externalAddress: "localhost:12345", + externalAddressPort: "12345", + }, + }, + { + name: "MissingExternalAddress", + config: &net.Config{}, + wantErr: true, + }, + { + name: "InvalidExternalAddress", + config: &net.Config{ExternalAddress: "invalid-address"}, + wantErr: true, + }, + { + name: "EmptyHostInExternalAddress", + config: &net.Config{ExternalAddress: ":12345"}, + wantErr: true, + }, + { + name: "ZeroPortInExternalAddress", + config: &net.Config{ExternalAddress: "localhost:0"}, + wantErr: true, + }, + { + name: "InvalidPortInExternalAddress", + config: &net.Config{ExternalAddress: "localhost:invalid"}, + wantErr: true, + }, + { + name: "PortGreaterThanMaxUint16", + config: &net.Config{ExternalAddress: "localhost:65536"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.Validate() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want.maxMsgSize, tt.config.MaxMessageSize) + require.Equal(t, tt.want.externalAddress, tt.config.ExternalAddress) + require.Equal(t, tt.want.externalAddressPort, tt.config.ExternalAddressPort()) + }) + } +} diff --git a/bridge/net/network.go b/bridge/net/network.go index 6278d570..0e5d4b81 100644 --- a/bridge/net/network.go +++ b/bridge/net/network.go @@ -25,10 +25,7 @@ import ( "io" "log" gonet "net" - "strconv" - "strings" - "github.com/google/uuid" "github.com/plgd-dev/device/v2/pkg/codec/cbor" "github.com/plgd-dev/device/v2/pkg/codec/json" "github.com/plgd-dev/device/v2/schema" @@ -43,14 +40,6 @@ import ( "github.com/plgd-dev/go-coap/v3/udp/server" ) -type Config struct { - ExternalAddress string `yaml:"externalAddress"` - MaxMessageSize uint32 `yaml:"maxMessageSize"` - externalAddressPort string `yaml:"-"` -} - -type RequestHandler func(req *Request) (*pool.Message, error) - type Net struct { cfg Config listener *net.UDPConn @@ -62,34 +51,6 @@ type Net struct { mux *mux.Router } -const DefaultMaxMessageSize = 2 * 1024 * 1024 - -func (cfg *Config) Validate() error { - if cfg.ExternalAddress == "" { - return fmt.Errorf("externalAddress is required") - } - host, portStr, err := gonet.SplitHostPort(cfg.ExternalAddress) - if err != nil { - return fmt.Errorf("invalid externalAddress: %w", err) - } - if host == "" { - return fmt.Errorf("invalid externalAddress: host cannot be empty") - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return fmt.Errorf("invalid externalAddress: %w", err) - } - if port == 0 { - return fmt.Errorf("invalid externalAddress: port cannot be 0") - } - if cfg.MaxMessageSize == 0 { - cfg.MaxMessageSize = DefaultMaxMessageSize - } - - cfg.externalAddressPort = portStr - return nil -} - // TODO: ipv6 + ipv6 multicast addresses func initConnectivity(listenAddress string) (*net.UDPConn, *net.UDPConn, error) { multicastAddr := "224.0.1.187:5683" @@ -213,65 +174,6 @@ func LoggingMiddleware(next mux.Handler) mux.Handler { }) } -type Request struct { - *pool.Message - Conn mux.Conn - Endpoints schema.Endpoints -} - -func (r *Request) Interface() string { - q, err := r.Queries() - if err != nil { - return "" - } - for _, query := range q { - if strings.HasPrefix(query, "if=") { - return strings.TrimPrefix(query, "if=") - } - } - return "" -} - -func (r *Request) URIPath() string { - p, err := r.Message.Options().Path() - if err != nil { - return "" - } - return p -} - -func (r *Request) DeviceID() uuid.UUID { - q, err := r.Queries() - if err != nil { - return uuid.Nil - } - for _, query := range q { - if strings.HasPrefix(query, "di=") { - deviceID := strings.TrimPrefix(query, "di=") - di, err := uuid.Parse(deviceID) - if err != nil { - return uuid.Nil - } - return di - } - } - return uuid.Nil -} - -func (r *Request) ResourceTypes() []string { - q, err := r.Queries() - if err != nil { - return nil - } - resourceTypes := make([]string, 0, len(q)) - for _, query := range q { - if strings.HasPrefix(query, "rt=") { - resourceTypes = append(resourceTypes, strings.TrimPrefix(query, "rt=")) - } - } - return resourceTypes -} - func (n *Net) ServeCOAP(w mux.ResponseWriter, request *mux.Message) { request.Hijack() go func(w mux.ResponseWriter, request *mux.Message) { diff --git a/bridge/net/request.go b/bridge/net/request.go new file mode 100644 index 00000000..6ddfbeb8 --- /dev/null +++ b/bridge/net/request.go @@ -0,0 +1,95 @@ +/**************************************************************************** + * + * Copyright (c) 2023 plgn.dev s.r.o. + * + * 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 implien. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +package net + +import ( + "errors" + "strings" + + "github.com/google/uuid" + "github.com/plgd-dev/device/v2/schema" + "github.com/plgd-dev/go-coap/v3/message/pool" + "github.com/plgd-dev/go-coap/v3/mux" +) + +type Request struct { + *pool.Message + Conn mux.Conn + Endpoints schema.Endpoints +} + +type RequestHandler func(req *Request) (*pool.Message, error) + +var ErrKeyNotFound = errors.New("key not found") + +func (r *Request) GetValueFromQuery(key string) (string, error) { + q, err := r.Queries() + if err != nil { + return "", err + } + prefix := key + "=" + for _, query := range q { + if strings.HasPrefix(query, prefix) { + return strings.TrimPrefix(query, prefix), nil + } + } + return "", ErrKeyNotFound +} + +func (r *Request) URIPath() string { + p, err := r.Message.Options().Path() + if err != nil { + return "" + } + return p +} + +func (r *Request) Interface() string { + v, err := r.GetValueFromQuery("if") + if err != nil { + return "" + } + return v +} + +func (r *Request) DeviceID() uuid.UUID { + v, err := r.GetValueFromQuery("di") + if err != nil { + return uuid.Nil + } + di, err := uuid.Parse(v) + if err != nil { + return uuid.Nil + } + return di +} + +func (r *Request) ResourceTypes() []string { + q, err := r.Queries() + if err != nil { + return nil + } + resourceTypes := make([]string, 0, len(q)) + for _, query := range q { + if strings.HasPrefix(query, "rt=") { + resourceTypes = append(resourceTypes, strings.TrimPrefix(query, "rt=")) + } + } + return resourceTypes +} diff --git a/bridge/net/request_test.go b/bridge/net/request_test.go new file mode 100644 index 00000000..526c7ae4 --- /dev/null +++ b/bridge/net/request_test.go @@ -0,0 +1,148 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgn.dev s.r.o. + * + * 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 implien. See the License for the specific + * language governing permissions and limitations under the License. + * + ****************************************************************************/ + +package net_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/device/v2/bridge/net" + "github.com/plgd-dev/go-coap/v3/message/pool" + "github.com/stretchr/testify/require" +) + +func TestGetValueFromQuery(t *testing.T) { + req := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + req.AddQuery("param1=value1") + req.AddQuery("param2=value2") + reqNoQuery := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + + tests := []struct { + req *net.Request + key string + wantErr bool + want string + }{ + {req, "param1", false, "value1"}, + {req, "param2", false, "value2"}, + {req, "param3", true, ""}, + {reqNoQuery, "param1", true, ""}, + } + + for _, tt := range tests { + v, err := tt.req.GetValueFromQuery(tt.key) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, v) + } +} + +func TestURIPath(t *testing.T) { + req := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + const path = "/path/to/resource" + err := req.SetPath(path) + require.NoError(t, err) + require.Equal(t, path, req.URIPath()) + + reqNoPath := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + require.Equal(t, "", reqNoPath.URIPath()) +} + +func TestInterface(t *testing.T) { + req := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + req.AddQuery("q1=v1") + req.AddQuery("if=interface") + req.AddQuery("q2=v2") + require.Equal(t, "interface", req.Interface()) + + reqNoInterface := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + reqNoInterface.AddQuery("q1=v1") + require.Equal(t, "", reqNoInterface.Interface()) + + reqNoQuery := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + require.Equal(t, "", reqNoQuery.Interface()) +} + +func TestDeviceID(t *testing.T) { + req := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + req.AddQuery("q1=v1") + deviceID := uuid.New() + req.AddQuery("di=" + deviceID.String()) + req.AddQuery("q2=v2") + require.Equal(t, deviceID, req.DeviceID()) + + reqInvalidDeviceID := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + reqInvalidDeviceID.AddQuery("di=invalid") + require.Equal(t, uuid.Nil, reqInvalidDeviceID.DeviceID()) + + reqNoDeviceID := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + reqNoDeviceID.AddQuery("q1=v1") + require.Equal(t, uuid.Nil, reqNoDeviceID.DeviceID()) + + reqNoQuery := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + require.Equal(t, uuid.Nil, reqNoQuery.DeviceID()) +} + +func TestResourceTypes(t *testing.T) { + req := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + req.AddQuery("q1=v1") + req.AddQuery("rt=type1") + req.AddQuery("rt=type2") + req.AddQuery("q2=v2") + require.Equal(t, []string{"type1", "type2"}, req.ResourceTypes()) + + reqNoResourceTypes := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + reqNoResourceTypes.AddQuery("q1=v1") + require.Equal(t, []string{}, reqNoResourceTypes.ResourceTypes()) + + reqNoQuery := &net.Request{ + Message: pool.NewMessage(context.Background()), + } + require.Nil(t, reqNoQuery.ResourceTypes()) +} diff --git a/bridge/resources/cbor.go b/bridge/resources/cbor.go new file mode 100644 index 00000000..0c0544c8 --- /dev/null +++ b/bridge/resources/cbor.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources + +import "github.com/plgd-dev/device/v2/pkg/codec/cbor" + +func MergeCBORStructs(a ...interface{}) interface{} { + var merged map[interface{}]interface{} + for _, v := range a { + if v == nil { + continue + } + data, err := cbor.Encode(v) + if err != nil { + continue + } + var m map[interface{}]interface{} + err = cbor.Decode(data, &m) + if err != nil { + continue + } + if merged == nil { + merged = m + } else { + for k, v := range m { + merged[k] = v + } + } + } + return merged +} diff --git a/bridge/resources/device/resource_internal_test.go b/bridge/resources/cbor_test.go similarity index 66% rename from bridge/resources/device/resource_internal_test.go rename to bridge/resources/cbor_test.go index 9a390646..31652f78 100644 --- a/bridge/resources/device/resource_internal_test.go +++ b/bridge/resources/cbor_test.go @@ -1,8 +1,27 @@ -package device +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources_test import ( "testing" + "github.com/plgd-dev/device/v2/bridge/resources" "github.com/stretchr/testify/require" ) @@ -83,7 +102,7 @@ func TestMergeCBORStructs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := mergeCBORStructs(tt.args.structs...) + got := resources.MergeCBORStructs(tt.args.structs...) if tt.want != nil { require.Equal(t, tt.want, got) return diff --git a/bridge/resources/device/resource.go b/bridge/resources/device/resource.go index 83862f94..c0fa455d 100644 --- a/bridge/resources/device/resource.go +++ b/bridge/resources/device/resource.go @@ -59,32 +59,6 @@ func New(uri string, dev Device, getAdditionalProperties GetAdditionalProperties return d } -func mergeCBORStructs(a ...interface{}) interface{} { - var merged map[interface{}]interface{} - for _, v := range a { - if v == nil { - continue - } - data, err := cbor.Encode(v) - if err != nil { - continue - } - var m map[interface{}]interface{} - err = cbor.Decode(data, &m) - if err != nil { - continue - } - if merged == nil { - merged = m - } else { - for k, v := range m { - merged[k] = v - } - } - } - return merged -} - func (d *Resource) Get(request *net.Request) (*pool.Message, error) { additionalProperties := d.getAdditionalProperties() deviceProperties := device.Device{ @@ -98,7 +72,7 @@ func (d *Resource) Get(request *net.Request) (*pool.Message, error) { deviceProperties.ResourceTypes = d.Resource.ResourceTypes deviceProperties.Interfaces = d.Resource.ResourceInterfaces } - properties := mergeCBORStructs(additionalProperties, deviceProperties) + properties := resources.MergeCBORStructs(additionalProperties, deviceProperties) res := pool.NewMessage(request.Context()) res.SetCode(codes.Content) res.SetContentFormat(message.AppOcfCbor) diff --git a/bridge/resources/etag.go b/bridge/resources/etag.go index 48492065..ffd3c13c 100644 --- a/bridge/resources/etag.go +++ b/bridge/resources/etag.go @@ -1,3 +1,21 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources import ( diff --git a/bridge/resources/etag_test.go b/bridge/resources/etag_test.go new file mode 100644 index 00000000..7903aac3 --- /dev/null +++ b/bridge/resources/etag_test.go @@ -0,0 +1,60 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources_test + +import ( + "sync" + "testing" + + "github.com/plgd-dev/device/v2/bridge/resources" + "github.com/stretchr/testify/require" +) + +func TestGetETag(t *testing.T) { + var last uint64 + for i := 0; i < 1000; i++ { + etag := resources.GetETag() + require.Greater(t, etag, last) + last = etag + } +} + +func TestGetETagParallel(t *testing.T) { + const numRoutines = 1000 + + etagMap := make(map[uint64]struct{}) + var mutex sync.Mutex + + var wg sync.WaitGroup + wg.Add(numRoutines) + + for i := 0; i < numRoutines; i++ { + go func() { + defer wg.Done() + etag := resources.GetETag() + mutex.Lock() + etagMap[etag] = struct{}{} + mutex.Unlock() + }() + } + + wg.Wait() + + require.Len(t, etagMap, numRoutines) +} diff --git a/bridge/resources/resource.go b/bridge/resources/resource.go index 3451adda..ab78e8f8 100644 --- a/bridge/resources/resource.go +++ b/bridge/resources/resource.go @@ -27,7 +27,6 @@ import ( "io" "reflect" - "github.com/google/uuid" "github.com/plgd-dev/device/v2/bridge/net" "github.com/plgd-dev/device/v2/pkg/codec/cbor" "github.com/plgd-dev/device/v2/schema" @@ -46,14 +45,6 @@ type CreateSubscriptionFunc func(req *net.Request, handler func(msg *pool.Messag const PublishToCloud schema.BitMask = 1 << 7 -func ToUUID(id string) uuid.UUID { - v, err := uuid.Parse(id) - if err != nil { - return uuid.NewSHA1(uuid.NameSpaceURL, []byte(id)) - } - return v -} - type subscription struct { done <-chan struct{} cancel func() diff --git a/bridge/resources/uuid.go b/bridge/resources/uuid.go new file mode 100644 index 00000000..91b87753 --- /dev/null +++ b/bridge/resources/uuid.go @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources + +import "github.com/google/uuid" + +func ToUUID(id string) uuid.UUID { + v, err := uuid.Parse(id) + if err != nil { + return uuid.NewSHA1(uuid.NameSpaceURL, []byte(id)) + } + return v +} diff --git a/bridge/resources/uuid_test.go b/bridge/resources/uuid_test.go new file mode 100644 index 00000000..0e9090bd --- /dev/null +++ b/bridge/resources/uuid_test.go @@ -0,0 +1,59 @@ +/**************************************************************************** + * + * Copyright (c) 2024 plgd.dev s.r.o. + * + * 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 resources_test + +import ( + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/device/v2/bridge/resources" + "github.com/stretchr/testify/require" +) + +func TestToUUID(t *testing.T) { + type args struct { + id string + } + tests := []struct { + name string + args args + want uuid.UUID + }{ + { + name: "valid", + args: args{ + id: "00000000-0000-0000-0000-000000000001", + }, + want: uuid.MustParse("00000000-0000-0000-0000-000000000001"), + }, + { + name: "invalid", + args: args{ + id: "00000000-0000-0000-0000-0000000000", + }, + want: uuid.NewSHA1(uuid.NameSpaceURL, []byte("00000000-0000-0000-0000-0000000000")), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := resources.ToUUID(tt.args.id) + require.Equal(t, tt.want, got) + }) + } +}